复杂度分析
分析统计算法的执行效率和内存资源消耗(时空复杂度分析)
数据结构和算法解决的是快和省的问题,那么什么样的代码运行得更快,什么样的代码更省存储空间便是复杂度分析的重点。
1. 事后估计法
让写好的代码跑一遍,通过统计和监控的手段获得代码运行时间以及内存占用情况的分析法叫事后估计法。事后估计法也是复杂度分析的一种手段,不过局限性比较大:
- 测试结果依赖测试环境
- 测试结果受数据规模的影响(排序时数据的有序性影响,规模太小不能反应真实的性能)
2. 大O复杂度表示法
不依赖具体的测试数据,对算法的执行效率进行粗略估计的方法
2.1 导出规律
前提:
- 算法的执行效率,可以粗略地看作算法代码执行的时间。
- 假设每行代码执行时间均相同,为 unit_time
下面这个代码片段,第2行需要一个unit_time, 第3行执行了n遍所以需要n*unit_time,
第4,5行各执行了
n
2
n^2
n2遍所以共需要
2
n
2
2n^2
2n2*unit_time。所以整个代码片需要
T
(
n
)
=
(
2
n
2
+
n
+
1
)
T(n)=(2n^2+n+1)
T(n)=(2n2+n+1)*unit_time。
1. def cal(n) :
2. sum = 0
3. for i in range(n):
4. for j in range(n):
5. sum = sum + i * j
结论:
也即,算法中所有代码的执行时间 T(n) 与算法中所有代码的执行总次数
f
(
n
)
f(n)
f(n)成正比:
T
(
n
)
=
O
(
f
(
n
)
)
(2.1)
T(n) = O(f(n)) \tag {2.1}
T(n)=O(f(n))(2.1)
式(2.1)就是大O复杂度表示法,其中
n
n
n为数据规模,
O
O
O 表示正比于。所以借助大O复杂度表示法,我们就可以简单地以代码执行的总次数来估计算法执行时间。比如上面的例子中就是:
T
(
n
)
=
O
(
2
n
2
+
n
+
1
)
T(n)=O(2n^2+n+1)
T(n)=O(2n2+n+1)
2.2 渐进时间复杂度
大 O 时间复杂度也叫渐进时间复杂度(简称:时间复杂度),实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势。
另外,如果n 很大(特别特别大)时,所导出的复杂度表达式中的常量、系数以及低阶这三部分对于增长趋势的影响都可以忽略。所以2.1中的例子的时间复杂度可表示为 T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2)。
王争老师给出了三个分析代码复杂度时实用的方法:
- 只关注循环执行次数最多的一段代码
- 加法法则:总复杂度等于量级最大的那段代码的复杂度
- 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
实际上,不用刻意去套用,这些都是很自然的,只要记得最后忽略掉常量、系数和低阶三部分,以最大量级作为结果即可。
2.3 常见时间复杂度量级
图片来自《数据结构与算法之美》 王争
上图给出了常见的时间复杂度量级,大致可以分为以下两类:
- 多项式量级: 常量阶 O ( 1 ) O(1) O(1)、对数阶 O ( l o g n ) O(logn) O(logn)、线性阶 O ( n ) O(n) O(n)、线性对数阶 O ( n l o g n ) O(nlogn) O(nlogn)、平方阶 O ( n 2 ) O(n^2) O(n2)等
- 非多项式量级: 指数阶 O ( 2 n ) O(2^n) O(2n)、阶乘阶 O ( n ! ) O(n!) O(n!)
我们常说的NP问题(Non-Deterministic Polynomial,非确定多项式)也就是时间复杂度为非多项式量级的算法问题。
注:
- 常量阶
O
(
1
)
O(1)
O(1)
代码的执行时间不随 n 的增大而增长 - 对数阶
对数阶中,忽略对数的“底”,统一表示为 O(logn)
2.4 空间复杂度
空间复杂度(全称:渐进空间复杂度),表算法的存储空间与数据规模之间的增长关系。
常见空间复杂度量级: 常量阶
O
(
1
)
O(1)
O(1)、线性阶
O
(
n
)
O(n)
O(n)、平方阶
O
(
n
2
)
O(n^2)
O(n2)等