目录
在计算机科学和算法分析中,时间复杂度和空间复杂度是评估算法效率的两个关键指标。它们帮助我们理解算法在处理数据时所需资源的多少,从而指导我们做出更优的选择。本文将深入浅出地介绍这两个概念,并通过实例加以说明。说白了你在工作当中要让代码的效率变得更好,就需要注意
一、时间复杂度
定义
时间复杂度,简而言之,是指执行算法所需要的计算工作量随着问题规模(通常是输入数据的大小)增长的变化趋势。它关注的是算法运行时间与输入数据规模之间的关系,通常用大O符号表示(O(n)),忽略掉常数项、低阶项以及最高阶的系数。
常见的时间复杂度
- O(1) - 常数时间复杂度:算法的执行时间不随输入数据规模变化,如数组访问某个元素。
- O(log n) - 对数时间复杂度:二分查找就是一个典型的例子,每一步都将问题规模减半。
- O(n) - 线性时间复杂度:遍历数组或列表中的每个元素。
- O(n log n) - 线性对数时间复杂度:快速排序、归并排序等高效排序算法。
- O(n^2) - 平方时间复杂度:冒泡排序、选择排序等简单排序算法。
- O(n^3)、O(2^n)、O(n!) - 随着问题规模的增长,所需时间急剧增加,分别对应立方、指数、阶乘复杂度。
如何计算时间复杂度
计算方法
空间复杂度的计算方法与时间复杂度相似,也是关注随着问题规模增长,算法所需最大额外空间的变化趋势。
三、实例分析
上面的这些都不能让大家看出复杂度
这里主要举时间复杂度例子
实例1
实例2
实例3
实例4
实例5
- 找出基本操作:首先确定算法中的基本操作,即执行次数最多的操作。
- 确定问题规模:用n表示输入数据的大小。
- 计算增长数量级:分析基本操作的执行次数与n的关系,忽略低阶项和系数,只保留最高阶项。
-
二、空间复杂度
定义
空间复杂度衡量的是算法在运行过程中临时占用存储空间大小的变化情况,同样与问题规模有关。这包括了算法本身占用的空间以及算法运行过程中需要的额外空间。
重要性
在内存资源有限的环境下,特别是嵌入式系统或大规模数据处理中,空间复杂度是一个不可忽视的因素。高效利用内存可以提升系统性能,减少资源消耗。
常见的空间复杂度
- O(1):算法使用的额外空间不随输入数据规模改变,如原地排序算法。
- O(n):额外空间正比于输入数据规模,如某些动态规划问题中需要的数组。
- O(n^2):随着输入规模增大,所需空间平方增长,例如某些矩阵运算。
二、空间复杂度
定义
空间复杂度衡量的是算法在运行过程中临时占用存储空间大小的变化情况,同样与问题规模有关。这包括了算法本身占用的空间以及算法运行过程中需要的额外空间。
重要性
在内存资源有限的环境下,特别是嵌入式系统或大规模数据处理中,空间复杂度是一个不可忽视的因素。高效利用内存可以提升系统性能,减少资源消耗。
常见的空间复杂度
- O(1):算法使用的额外空间不随输入数据规模改变,如原地排序算法。
- O(n):额外空间正比于输入数据规模,如某些动态规划问题中需要的数组。
- O(n^2):随着输入规模增大,所需空间平方增长,例如某些矩阵运算。
计算方法
空间复杂度的计算方法与时间复杂度相似,也是关注随着问题规模增长,算法所需最大额外空间的变化趋势。
三、实例分析
//请计算一下Func1中++count语句总共执行了多少次?
void Funcl(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);
}
这个代码的时间复杂度是多少嘞?
这边我们看一下我们有一个嵌套循环,外循环执行一次,内循环就要执行N次,所以我们得出了N的平方,再看我们的第二个循环循环的是2*N,下面的while循环10次,由这些依据我们得到一个公式
F(N) = N^2+2*N+10
我们计算时间复杂度有一个大O的渐进表示法来表示,来看个例子:
精确值 估算值
N=10 F(N)=130 100
N=100 F(N)=10210 10000
N=1000 F(N)=1002010 1000000
实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这 里我们使用大O的渐进表示法。
大O的渐进表示法
由于我们得计算机,运算很快
这个也要看我们的电脑配置
大O渐进表示法,简单来说,是一种衡量算法随着输入数据量增加时,其执行时间或所需资源如何增长的方法。它不关注具体的计算时间,而是关心增长的趋势和速度。
想象一下,你正在研究两个不同的背包打包算法。一个算法检查每件物品与背包内所有可能位置的组合(这可能会导致检查的数量呈指数级增长),而另一个算法则采用更聪明的方法,比如按物品价值和重量预先排序(这样可能最多只需检查每个物品一次)。为了比较这两个算法哪个在面对大量物品时表现更好,我们就需要用到大O表示法。
通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。
举个例子吧,假如你今天在看我的博客,但是你女朋友给你发消息说晚上出来玩,然后叫你定个时间,于是就出来三种情况
最好情况(Best Case)6:00
- 描述:博客非常短,你几乎立刻就读完了。
- 算法复杂度:在这种情况下,无论博客多长,你都能瞬间完成阅读并回复消息。这类似于一个常数时间操作,可以用O(1)表示,意味着操作时间不随输入大小变化。
平均情况(Average Case)6:10
- 描述:博客的长度中等,既不太长也不太短,你需要花一些合理的时间阅读。
- 算法复杂度:如果大多数时候博客的长度在一个可预期的范围内,且阅读时间与博客长度成正比,那么这可以视为线性时间复杂度,即O(n),其中n代表博客的字数。这意味着阅读时间随博客长度线性增长。
最坏情况(Worst Case)6:20
- 描述:博客异常长,需要很长时间才能读完。
- 算法复杂度:在这种情况下,如果阅读时间直接与博客的长度成正比(忽略可能的加载时间等其他因素),复杂度仍然是O(n)。但如果考虑到随着博客越来越长,你的注意力可能会分散,导致阅读每个额外字词所需时间轻微增加,这种情况下虽然仍是线性关系,但强调了在极端条件下的体验。
这三种情况,你选哪一个最保险?才能不让你的女朋友等你嘞?所以我们有时候不要把预期拉的太高,做事要想好最坏的打算
通过这些我们回到上面的代码我们就可以得出他的时间复杂度是O(n^2),因为他影响最大,也是最坏的那个
接下来我们看几个例子来练习一下
例1:
//计算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);
F(M+N)
这里没有明确说明N远大于M还是M远大于N,都没有明确说明,那就是对执行次数影响是同等的,谁都不能舍去,所以结果为O(N)=N+M。
例2:
//计算Func4的时间复杂度?
void Func4(int N)
{
int count =0;
for (int k =0;k<100;++k)
{
++count;
}
printf("%d\n",count);
}
大家看一下这个时间复杂度是多少?
这个循环一共执行了100次,这个100是一个常数,所以我们这里的空间复杂度是O(1)(就是我们可以看出来的次数,执行次数不是未知数)
例3:
例4
这个就要根据逻辑理解这个O(2^N)
例5
空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。 空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法。
注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因 此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。