复杂度分析是用来分析算法在时间和空间维度的消耗趋势。
1. 为什么要做复杂度分析
因为现实中我们所拥有的设备资源和时间都是有限的,程序的运行亦是如此,我们希望消耗更小的硬件和时间去完成某项功能,我们也需要为运行一段程序去提供其所需要的资源,以及评估运行的时间是否可以接受。
仅通过跑一下程序来统计时间、空间的消耗不可以吗?
确实,跑一下程序能够得到一些实际的消耗数据,但是:
(1) 这需要先把程序实现出来,以及可执行环境,成本较高。而复杂度分析基于伪代码即可。
(2) 实际运行受测试数据样本、规模,设备性能,以及当时CPU、内存运行等诸多情况影响,得到的结果可能比较片面。
2. 复杂度的分类
2.1 渐进时间复杂度
简称"时间复杂度",用来表示随着数据规模的增加,算法执行所消耗的时间的变化趋势。
设数据规模为n, 代码执行时间为T(n), 代码行执行次数为f(n), 每行代码执行时间为unit_time,
则T(n)=f(n)*unit_time.
T(n)与代码行执行的次数成正比,记作T(n)=O(f(n))。
这就是大O表示法,f(n)中的低阶、常量、系数三部分并不左右增长趋势,所以都可以忽略。我们只需要记录一个最大量级就可以了。
2.1.1 分析时间复杂度的基本规则
只关注受数据规模影响的代码块。
只关注循环执行次数最多的一段代码,分析其执行次数和n的关系。
加法规则
如果T1(n)=O(f(n)), T2(n)=O(g(n)), T(n)=T1(n)+T2(n).
则T(n)=MAX(T1(n), T2(n))=MAX(O(f(n)), O(g(n)))=O(MAX(f(n), MAX(g(n)))
乘法规则
设T1(n)=O(f(n)), T2(n)=O(g(n)), T(n)=T1(n)*T2(n).
则T(n)=O(f(n))*O(g(n))=O(f(n)*g(n)).
忽略常数项,只关注最高阶项
2.1.2 常见时间复杂度
O(1), O(logn), O(n), O(nlogn), O(n²)
图1. 常见时间复杂度
图2. 常见时间复杂度趋势图
分析方法
最好情况时间复杂度
最坏情况复杂度
平均情况时间复杂度
均摊时间复杂度
2.2 渐进空间复杂度
简称“空间复杂度”,表示算法所需存储空间随着数据规模增长的关系。
常见的空间复杂度
有O(1)、O(n)、O(n²),而O(logn)、O(nlogn) 这样的对数阶复杂度平时都用不到。
3. 问题
那么我们是不是可以简单粗暴地说:在任何情况下,我们都应该选时间复杂度更低的算法呢?