数据结构与算法:复杂度

友友们大家好啊,今天开始正式学习数据结构与算法有关内容,后续不断更新数据结构有关知识内容,希望多多支持!

数据结构:
数据结构是用于存储和组织数据的方式,以便可以有效地访问和修改数据。不同的数据结构适用于不同类型的应用,并且具体的数据结构可以大幅影响程序的性能。数据结构分为两大类:线性数据结构和非线性数据结构。

算法:
算法是完成特定任务的一系列操作步骤,是解决问题的明确规范。算法的效率通常通过时间复杂度和空间复杂度来评估,即算法执行所需的时间和空间资源。

那么本节课我们进入第一节,复杂度!

复杂度

  • 时间复杂度
    • 大O的渐进表示法
    • 常见时间复杂度计算举例:
  • 空间复杂度
    • 例(十)计算Fibonacci的空间复杂度

算法效率
算法效率通常是指算法运行所需的资源量,评价算法效率主要依据两个重要指标:时间复杂度和空间复杂度

时间复杂度

时间复杂度是在计算机科学中衡量一个算法执行所需时间的量化指标。更准确来说,它不直接度量实际的时间(如秒或毫秒),而是衡量算法需要执行的操作步骤数量。计算时间复杂度通常假设每个基本操作的执行时间是固定和相同的,即使在现实中不同的操作可能需要不同的时间。算法中的基本操作的执行次数,为算法的时间复杂度

即:找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度。

例:请计算一下Func1中++count语句总共执行了多少次?

void Func1(int N)
{
int count = 0;
for (int i = 0; i < N ; ++ i)
{for (int j = 0; j < N ; ++ j){++count;}
}for (int k = 0; k < 2 * N ; ++ k)
{++count;
}
int M = 10;
while (M--)
{++count;
}
printf("%d\n", count);
}

Func1 执行的基本操作次数F(N)=N2+2*N+10;

  • N=10,F(N)=130
  • N=100,F(N)=10210
  • N=1000,F(N)=1002010

实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法

大O的渐进表示法

大O渐进表示法是数学和计算机科学中用来描述函数增长率的一种表示方法。它是分析算法复杂度(如时间复杂度和空间复杂度)时最常用的工具之一。大O表示法通过忽略常数因子和低阶项,专注于描述最主要的影响因素,从而提供了一种比较算法效率的方法。

定义

大O符号,记作O(f(n)),表示随着输入大小n的增加,算法的运行时间或所需空间的增长率与f(n)增长率相同或者更慢。在这里,f(n)是一个数学函数,代表随着输入规模n的变化,算法的资源消耗如何变化。

关键概念

  • 最坏情况分析:大O通常表示最坏情况下的复杂度,即算法在任何输入上的性能不会比这个界限更差。

  • 渐进上界:大O表示的是一个上界,说明了算法复杂度的一个上限。

  • 忽略非主要项:在大O表示法中,我们只关注主要项(即最大影响项),忽略常数因子和低阶项。

推导大O阶方法

  • 用常数1取代运行时间中的所有加法常数
  • 在修改后的运行次数函数中,只保留最高阶项
  • 如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶

有些算法的时间复杂度存在最好、平均和最坏情况(例四):

  • 最坏情况:任意输入规模的最大运行次数(上界)
  • 平均情况:任意输入规模的期望运行次数
  • 最好情况:任意输入规模的最小运行次数(下界)

例如上述例题:
Func1 执行的基本操作次数F(N)=N2+2*N+10;

使用大O的渐进表示法以后,Func1的时间复杂度为:O(N2)

  • N = 10 F(N) = 100
  • N = 100 F(N) = 10000
  • N = 1000 F(N) = 1000000

通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。

常见时间复杂度计算举例:

例(一):计算Func2的时间复杂度

void Func2(int N)
{int count = 0;for (int k = 0; k < 2 * N ; ++ k){++count;}int M = 10;while (M--){++count;}printf("%d\n", count);
}

基本操作执行了2N+10次,通过推导大O阶方法知道,时间复杂度为 O(N)

例(二):计算Func3的时间复杂度

void Func3(int N, int M)
{int count = 0;for (int k = 0; k < M; ++ k){++count;}for (int k = 0; k < N ; ++ k){++count;}printf("%d\n", count);
}

基本操作执行了M+N次,有两个未知数M和N,时间复杂度为 O(N+M)

例(三):计算Func4的时间复杂度

void Func4(int N)
{int count = 0;for (int k = 0; k < 100; ++ k){++count;}printf("%d\n", count);
}

基本操作执行了10次,通过推导大O阶方法,时间复杂度为 O(1)

O(1)代表常数次!!!

例(四):计算strchr的时间复杂度

const char * strchr ( const char * str, int character )
{
while(*str)
{if(*str==character)return str;++str;
}
}

这道题就涉及最好情况,平均情况和最坏情况

  • 最好情况
    最好情况发生在要查找的字符 character 正好是字符串 str 的第一个字符。此时,循环会在第一次迭代时找到匹配,立即返回指向该字符的指针。在这种情况下,该函数的时间复杂度为 O(1),因为无论字符串多长,只需进行一次比较操作。

  • 平均情况:
    平均情况会假设字符在字符串中均匀分布或者一定概率出现在任何位置。由于字符可以出现在字符串的任何位置,因此平均而言,我们可能需要检查字符串的一半才能找到字符或确定字符不在字符串中。平均来看,时间复杂度与字符串的长度成正比,即 O(N/2),但由于大O表示法忽略常数因子,因此简化为 O(N),其中 N 是字符串的长度。

  • 最坏情况
    最坏情况发生在两种情况下:

    • 要查找的字符不存在于字符串中,则必须遍历整个字符串直至终结符 ‘\0’,进行 N 次比较,其中 N 是字符串的长度。
    • 要查找的字符正好是字符串的最后一个字符(紧邻终结符 ‘\0’),这同样需要遍历整个字符串。

时间复杂度一般看最坏所以时间复杂度为 O(N)

例(五):计算BubbleSort的时间复杂度

void BubbleSort(int* a, int n)
{assert(a);for (size_t end = n; end > 0; --end){int exchange = 0;for (size_t i = 1; i < end; ++i){if (a[i-1] > a[i]){Swap(&a[i-1], &a[i]);exchange = 1;}}if (exchange == 0)break;}
}

这道题中有两层循环
最坏的情况发生在数组完全逆序。在这种情况下,我们需要对每一个元素做完整的n-1次比较和可能的交换,然后是n-2次,直到最后一次比较。集合中的比较次数 T 可以用以下等式来表示:

T = (n-1) + (n-2) + ... + 2 + 1 = n(n-1)/2

当 n 逐渐增加到非常大时,n2项占据了主导,因此我们可以将时间复杂度简化为 O(n2)

例(六):计算BinarySearch的时间复杂度**(二分查找)**

int BinarySearch(int* a, int n, int x)
{assert(a);int begin = 0;int end = n-1;while (begin <= end){int mid = begin + ((end-begin)>>1);if (a[mid] < x)begin = mid+1;else if (a[mid] > x)end = mid-1;elsereturn mid;}return -1;
}

在每次迭代中,二分查找都会将搜索范围减半。如果数组的大小为n,则迭代如下:

  • 第一次迭代后,搜索范围减为n/2。
  • 第二次迭代后,搜索范围减为n/4。

这一过程持续进行,直到搜索范围无法再分割(即begin > end)。划分过程的次数可以用对数函数表示:

n, n/2, n/4, ..., 1

当我们达到1时,相当于进行了k次迭代,那么n/2k = 1。解这个等式,得到n = 2k。取对数可得k = log₂(n)。

因此,二分查找的时间复杂度是O(log n)。

注意:这里的对数底数是2是因为每次迭代都将搜索区间分为两部分。二分查找的效率与目标值的实际位置无关,从最坏情况来看总是O(log n)。

例(七) 计算阶乘递归Fac的时间复杂度

long long Fac(size_t N)
{
if(0 == N)
return 1;
return Fac(N-1)*N;
}

每次递归调用减少 N 的值,直到 N 达到 0。对于每个 N,函数只进行一次递归调用。因此,如果初始值为 N,那么会有 N 次递归调用。所以这个函数的时间复杂度是 O(N)。

例(八) 计算斐波那契递归Fib的时间复杂度

long long Fib(size_t N)
{
if(N < 3)
return 1;
return Fib(N-1) + Fib(N-2);
}

在这里插入图片描述
以 Fib(5) 为例,其递归调用树大致如下:

         Fib(5)/      \Fib(4)     Fib(3)/    \      /    \
Fib(3) Fib(2) Fib(2) Fib(1)/   \
Fib(2) Fib(1)

从这个树状结构中我们可以看到:

  • Fib(5) 分解为 Fib(4) 和 Fib(3)
  • Fib(4) 分解为 Fib(3) 和 Fib(2)
  • Fib(3) 分解为 Fib(2) 和 Fib(1)

以此类推,直到基础情况 Fib(1) 或 Fib(2),返回 1。

注意在这棵树中,Fib(3) 被计算了两次,Fib(2) 被计算了三次。随着 N 的增大,重复计算的问题会指数性增长。

在这样的递归调用中,每增加一个 N,递归树的层数加一,每一层的结点数几乎是上一层的两倍(除了在接近底部,当 N 小于 3 时,不再继续拆分)。

因此,如果我们考虑每个函数调用是树中的一个节点,那么整个递归过程涉及的节点总数(即函数调用的总数)大约是一个满二叉树中的节点数,这是因为除了最底层,几乎每个节点都会分裂成两个子节点。

满二叉树的节点总数与树的深度(在这里即N)有关,大约是 2N(为简化分析,这里忽略了精确的计数,特别是在树的最底层)。因此,递归解决方案的时间复杂度被认为是指数级的,即 O(2N)。

空间复杂度

空间复杂度是一个衡量算法在运行过程中临时占用存储空间大小的一个量度,它和时间复杂度一样,是用来评价算法效率的一个重要指标。空间复杂度不仅包括在算法执行过程中,输入和输出所占据的空间,还包括算法执行过程中临时占用的额外空间。

空间复杂度算的是变量的个数。空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法

注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。

举例如下:

例(九) 计算BubbleSort的空间复杂度

void BubbleSort(int* a, int n)
{
assert(a);for (size_t end = n; end > 0; --end){int exchange = 0;for (size_t i = 1; i < end; ++i){if (a[i-1] > a[i]){Swap(&a[i-1], &a[i]);exchange = 1;}}if (exchange == 0)break;}
}

BubbleSort的空间复杂度计算主要依据算法在执行过程中所需的额外内存空间。在讨论空间复杂度时,我们关注的是除了输入数据本身占用的空间外,算法运行时所需的附加空间。这通常包括栈空间(用于存储函数调用和局部变量)和堆空间(用于动态内存分配)。然而,在冒泡排序的实现中,我们主要关注的是栈空间的使用。

让我们逐一查看 BubbleSort 函数中的元素:

  1. 局部变量
    • end: 用于标记数组中尚未排序部分的末尾。
    • exchange: 用于标记在一次遍历中是否发生了交换,以此判断数组是否已经排序完成。
    • i: 循环计数器,用于遍历数组中的元素。
  2. 参数:
    • a: 指向数组的指针,它引用了函数外部的数组,因此不增加内部空间复杂度。
    • n: 表示数组的大小,是一个整型值。
  3. 递归调用:
    冒泡排序是一个迭代算法,不涉及递归调用,因此不会因为递归调用导致栈空间显著增加。
  4. 动态分配的内存:
    在此实现中,没有动态分配的内存;算法仅在原始数组上进行操作。

计算空间复杂度

  • 固定大小的局部变量: end、exchange 和 i 是固定大小的整型变量,它们占用的空间量与数组的大小 n 无关。
  • 无递归调用: 算法不使用递归,因此不会因为递归调用而在栈上占用额外的空间。
  • 无动态内存分配: 算法运行过程中没有使用如 malloc, new 等动态内存分配函数,因此不会在堆上占用额外的空间。

BubbleSort 函数的空间复杂度主要由固定数量的局部变量决定,这意味着它的空间需求不随输入数组的大小 n 而变化。根据空间复杂度的定义,我们可以得出 BubbleSort 的空间复杂度是 O(1),即常量空间复杂度。

例(十)计算Fibonacci的空间复杂度

long long* Fibonacci(size_t n)
{if(n==0)return NULL;long long * fibArray = (long long *)malloc((n+1) * sizeof(long long));fibArray[0] = 0;fibArray[1] = 1;for (int i = 2; i <= n ; ++i){fibArray[i] = fibArray[i - 1] + fibArray [i - 2];}return fibArray;返回斐波那契数列的前n项
}

空间复杂度分析

  1. 动态内存分配:

    • 这个函数中最重要的部分是 fibArray 的动态内存分配。分配的空间大小直接与输入 n 成正比。即 fibArray 的大小是 n+1 个 long long 类型的大小。
      固定大小的局部变量:
  2. i 是一个整型变量,它的大小是固定的,与 n 无关。

因为函数的主要内存消耗来自于与输入大小成正比的 fibArray,所以 Fibonacci 函数的空间复杂度是 O(n)。这表明所需的存储空间随着输入 n 的增长而线性增长。

例(十一) 计算阶乘递归Fac的空间复杂度

long long Fac(size_t N)
{if(N == 0)return 1;return Fac(N-1)*N;
}

空间复杂度分析

  1. 递归调用:
    • 这个函数的关键在于它使用了递归调用。每一次递归调用都需要在调用栈上增加一层,用于保存当前调用的状态(包括局部变量和参数)。
  2. 递归深度:
    • 递归的深度直接与输入参数 N 相关。对于每个大于 0 的 N,都会产生一个递归调用,直到 N 降至 0。

由于递归调用会造成调用栈大小随 N 线性增长,因此 Fac 函数的空间复杂度是 O(N)。这意味着随着 N 的增大,函数的内存占用也以线性方式增加。

第一节知识点大概到此结束,后续我们会根据遇见的题再进行分析,感谢大家的阅读!!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/652577.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1.23神经网络框架(sig函数),逆向参数调整法(梯度下降法,链式法则(理解,及处理多层神经网络的方式))

框架 输入层 隐藏层 存在一个阈值&#xff0c;如果低于某一阈值就不激活&#xff1b;高于了就激活 输出层 逆向参数调整方法 初始阶段&#xff0c;随机设置权重值w1,w2 依据训练集 两个数学方法 &#xff08;梯度下降、链式法则&#xff09; 调参借助两个数学方法 当导数为…

2024.1.27 GNSS 学习笔记

1.精确的描述轨道的一组数据(星历)是实现精确定位与导航的基础。 2.GNSS卫星广播星历的提供方式一般有两种&#xff1a;一种是提供开普勒轨道参数和必要的轨道摄动改正项参数&#xff0c;如GPS、BDS、Galileo三大系统采用此种模式&#xff0c;还有QZSS系统&#xff1b;另一种是…

Spring Cloud 之Config详解

大家好&#xff0c;我是升仔 在微服务架构中&#xff0c;统一的配置管理是维护大规模分布式系统的关键。Spring Cloud Config为微服务提供集中化的外部配置支持&#xff0c;它可以与各种源代码管理系统集成&#xff0c;如Git、SVN等。本文将详细介绍如何搭建配置服务器、管理客…

用ASM HEMT模型提取GaN器件的参数

标题&#xff1a;Physics-Based Multi-Bias RF Large-Signal GaNHEMT Modeling and Parameter Extraction Flow (JEDS 17年) 模型描述 该模型的核心是对表面势&#xff08;ψ&#xff09;及其随施加的栅极电压&#xff08;Vg&#xff09;和漏极电压&#xff08;Vd&#xff09…

C++ STL中list迭代器的实现

list 的模拟实现中&#xff0c;重难点在于迭代器功能的实现&#xff0c;因此本文只围绕 iterator 及 const_iterator 的设计进行介绍&#xff0c;其余如增删查改则不再赘述——在C语言的基础上&#xff0c;这些都非常简单。 与 string / vector 不同&#xff0c;list 的节点原生…

【时间安排】

最近刚刚回到家&#xff0c;到家就是会有各种事情干扰&#xff0c;心里变乱人变懒的&#xff0c;而要做的事情也要继续&#xff0c;写论文&#xff0c;改简历&#xff0c;学习新技能。。 明天后天两天写论文改简历 周一&#xff08;早上去城市书房&#xff0c;可能吵一点戴个耳…

【深度学习:开源BERT】 用于自然语言处理的最先进的预训练

【深度学习&#xff1a;开源BERT】 用于自然语言处理的最先进的预训练 是什么让 BERT 与众不同&#xff1f;双向性的优势使用云 TPU 进行训练BERT 结果让 BERT 为您所用 自然语言处理 &#xff08;NLP&#xff09; 面临的最大挑战之一是训练数据的短缺。由于 NLP 是一个具有许多…

C#学习(十一)——Array和Collection

一、集合 集合重要且常用 孤立的数据是没有意义的&#xff0c;集合可以作为大量数据的处理&#xff0c;可进行数据的搜索、迭代、添加、删除。 C#中&#xff0c;所有集合都必须实现ICollection接口&#xff08;数组Array除外&#xff09; 集合说明Array数组&#xff0c;固定长…

【GitHub项目推荐--基于 AI 的口语训练平台】【转载】

Polyglot Polyglot 是一个开源的基于 AI 的口语训练平台客户端&#xff0c;可以在 Windows、Mac 上使用。 比如你想练习英语口语&#xff0c;只需在该平台配置一个虚拟的 AI 国外好友&#xff0c;你可以通过发语音的方式和 AI 好友交流&#xff0c;通过聊天的方式提升你的口…

中仕教育:事业单位考试考什么?

事业单位考试分为两个阶段&#xff0c;分别是笔试和面试&#xff0c;考试科目包括公共科目和专业科目两部分。 公共科目内容是公共基础知识、职业能力测试或申论。一种形式为&#xff1a;公共基础知识职业能力测试或职业能力测试申论。另一种形式为&#xff1a;公共基础申论。…

c语言基础6

1.逗号表达式 逗号表达式&#xff0c;就是用逗号隔开的多个表达式。 逗号表达式&#xff0c;从左向右依次执行。整个表达式的结果是最后⼀个表达式的结果。 我们来看下面的一个代码&#xff1a; int main() {int a 1;int b 2;int ret (a > b, a b 2, b, b a 1);p…

Nginx实现反向代理负载均衡实验

实验环境&#xff1a; VM REdhat虚拟机&#xff08;192.168.87.5&#xff09;一台、VM Redhat虚拟机&#xff08;192.168.87.3&#xff09;一台、阿里云服务器&#xff08;47.93.79.92&#xff09;一台 实验要求&#xff1a;通过windows浏览器访问192.168.87.5&#xff08;虚…

《合成孔径雷达成像算法与实现》Figure5.19

clc clear close all距离向参数 R_eta_c 20e3; % 景中心斜距 Tr 25e-6; % 发射脉冲时宽 Kr 0.25e12; % 距离向调频率 Fr 7.5e6; % 距离向采样率 Nrg 256; % 距离线采样点数 Bw abs(Kr*Tr); …

仰暮计划|“她就是用她的一双小脚把我们兄弟姐妹几个拉扯大的”

在残存的一些老物件中&#xff0c;在一些泛黄的相片中&#xff0c;掩藏着岁月的冲刷和青葱的时光。曾经无忧无虑的少女早已白发苍苍&#xff0c;不复青春貌美&#xff1b;曾经在父母面前笑闹的孩子早已变成他人眼中的长辈。 ——题记 她的身影也许并不高大&#xff0c;甚至还略…

Linux编辑器vim(含vim的配置)

文章目录 前言vim的基本概念vim基本操作进入vim模式切换退出vim vim指令vim命令模式指令vim底行模式命令 简单vim配置 前言 本篇文章&#xff0c;小编将介绍Linux编辑器–>vim以及vim的配置。 vim的基本概念 正常/普通/命令模式(Normal mode) 控制屏幕光标的移动&#xf…

Gin 框架之jwt 介绍与基本使用

文章目录 一.JWT 介绍二.JWT认证与session认证的区别2.1 基于session认证流程图2.2 基于jwt认证流程图 三. JWT 的构成3.1 header : 头部3.2 payload : 负载3.2.1 标准中注册的声明 (建议但不强制使用)3.2.2 公共的声明3.2.3 私有的声明3.2.4 定义一个payload 3.3 signatrue : …

【计算机网络】概述|分层体系结构|OSI参考模型|TCP/IP参考模型|网络协议、层次、接口

目录 一、思维导图 二、计算机网络概述 1.计算机网络定义、组成、功能 2.计算机网络分类 3.计算机网络发展历史 &#xff08;1&#xff09;计算机网络发展历史1&#xff1a;ARPANET->互联网 &#xff08;2&#xff09;计算机网络发展历史2&#xff1a;三级结构因特网 …

jenkins对接K8S

创建连接K8S的凭据 查看需要使用到的命名空间 [rootk8s ~]# kubectl get ns |grep arts-system arts-system Active 16d创建service accounts [rootk8s ~]# kubectl create sa jenkins-k8s -n arts-system serviceaccount/jenkins-k8s created [rootk8s ~]# kubectl…

一键批量处理,轻松为HTML文本添加所需内容

你是否曾经遇到过需要批量处理大量HTML文本的情况&#xff1f;是否曾经因为一个个编辑而感到繁琐和无趣&#xff1f;现在&#xff0c;这些问题都可以得到轻松解决&#xff01;我们的文本批量处理工具&#xff0c;为你提供一站式的服务&#xff0c;让你告别重复繁琐的操作&#…

java eazyexcel 实现excel的动态多级联动下拉列表(2)使用MATCH+OFFSET函数

原理 同样是将数据源放到一个新建的隐藏的sheet中&#xff0c;第一行是第一个列表的数据&#xff0c;第二行是每一个有下级菜单的菜单&#xff0c;他下面的行就是他下级菜单的每一值使用MATCH函数从第二行找到上级菜单对应的列根据OFFSET函数从2中获取的列&#xff0c;取得下级…