为什么需要复杂度分析?
事后统计法
把代码跑一遍,通过统计、监控,就能得到算法执行的时间和占用的内存大小。
事后统计法的局限性
1. 测试结果依赖测试环境
2. 测试结果受数据规模影响
所以,我们需要一个不用具体的测试数据来测试,就可以粗略地估计算法的执行效率的方法。就是时间复杂度分析法。
大O复杂度表示法
T(n) = O(f(n))
T(n) 表示代码执行的时间
n 表示数据规模的大小
f(n) 表示每行代码执行的次数总和
O 就是这个表示法的一个符号
大 O 时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以,也叫作渐进时间复杂度,简称时间复杂度。
如何分析一段代码的时间复杂度?
1. 只关注循环执行次数最多的一段代码
2. 加法法则:总复杂度等于量级最大的那段代码的复杂度
3. 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
常见时间复杂度
常数阶
def cal():
# 常数阶
a = 10 # 执行一次
sum = (a+2) * 5 / 2 # 执行一次
return sum # 执行一次
复杂度:T(n) = O(3)
线性阶
def cal():
# 线性阶
n = 100 # 执行一次
sum = 0 # 执行一次
for i in range(n): # 执行n次
sum += i # 执行n次
return sum # 执行一次
复杂度:T(n) = O(2n+3)
对数阶
def cal():
# 对数阶
n = 100 # 执行一次
c = 1 # 执行一次
while c < n: # 每次c乘2距离n就更近,有x次2相乘后会大于n,2^x=n 得到x=log2n,执行log2n次
c = c * 2 # 每次c乘2距离n就更近,有x次2相乘后会大于n,2^x=n 得到x=log2n,执行log2n次
复杂度:T(n) = O(2log2n+2),log2n中的2是底数
平方阶
def cal():
# 平方阶
n = 100 # 执行一次
sum = 0 # 执行一次
for i in range(n): # 执行n次
print(i) # 执行n次
for j in range(n): # 执行n²次
sum += j # 执行n²次
return sum # 执行一次
复杂度:T(n) = O(2n²+2n+3)
当 n 很大时,你可以把它想象成 10000、100000。而公式中的低阶、常量、系数三部分并不左右增长趋势,所以都可以忽略。
我们只需要记录一个最大量级就可以了。
比如:
T(n) = O(3),大O表示法记作:T(n) = O(1)
T(n) = O(2n+3),大O表示法记作:T(n) = O(n)
T(n) = O(2log2n+2),大O表示法记作:T(n) = O(logn)
T(n) = O(2n²+2n+3),大O表示法记作:T(n) = O(n²)
越高阶复杂度的算法,执行效率越低。
从低阶到高阶
时间复杂度之间的关系