1.把算法的代价看作规模的函数之后,很容易看到一种必然出现的情况:
可能有一些算法,随着实例规模的增长,其时间(或空间)开销的增长非常快,
而另一些算法的开销函数随着规模增长而增长的比较慢,
这两个函数关系称为算法的时间代价和空间代价。
2.人们主要关注算法的最坏情况代价,有时也关注算法的平均代价,而算法的最乐观的估计基本上没有价值。
3.对于算法的时间和空间性质,最重要的是其量级和趋势,这些是代价的主要部分。
例如:可以认为3n²和100n²属于同一个量级
4.”大O记法“
假设存在函数g,使得算法A处理规模为n的问题实例所用的时间T(n)=O(g(n)),则称O(g(n))为算法A的渐进时间复杂度,简称时间复杂度。
算法的空间复杂度S(n)的定义与此类似。S(n)=O(g(n) )
最常用的渐进复杂度函数:
O(1),O(log n),O(n),O(n log n),O(n²),O(n³),O(2n)
5.基本循环程序
这里只考虑算法的时间复杂度,基本循环程序只有顺序组合,条件分支和循环结构。
分析算法的几条基本计算规则:
0.基本操作,认为其时间复杂度为O(1)。如果是函数调用,应该将其时间复杂度代入,参与整体时间复杂度的计算。
(1)加法规则(顺序组合)。如果算法(或所考虑算法片段)是两个部分(或多个部分)的顺序组合,其复杂性是这两部分(或多部分)的复杂性之和。以两个部分为例:
1(n)+T2(n) = O(T1(n))+O(T2(n)) = O(max(T1(n),T2(n)))
其中T1(n)和T2(n)分别为顺序组合的两个部分的时间复杂度。由于忽略了常量因子,加法等价于求最大值,取T1(n)和T2(n)中复杂度较高的一个。
(2)乘法规则(循环结构)。如果算法(或所考虑的算法片段)是一个循环,循环体将执行T1(n)次,每次执行需要T2(n)时间,那么:
T(n) = T1(n) * T2(n) = O(T1(n))*O(T2(n)) = O(T1(n) * T2(n))
(3)取最大规则(分支结构)。如果算法(或所考虑算法片段)是条件分支,两个分支的时间复杂性分别为T1(n)和T2(n),则有:
T(n) = O(max(T1(n),T2(n)))
几个例子:
for i in range(n):
for j in range(n):
x = 0.0
for k in range(n):
x = x + m1[i][k] * m2[k][j]
m[i][j] = x
时间复杂度为:
T(n) = O(n)*O(n)*(O(1)+O(n)*O(1)+O(1))
= O(n)*O(n)*O(n)
= O(n*n*n)
= O(n³)
data=[]
while 还有数据:
x = 下一数据
data.insert(0,x) # 表新数据加到表的最前面
时间复杂度为:
T(n) = O(1)+O(n)*(O(1)+O(n))
= O(n²)
data=[]
while 还有数据:
x = 下一数据
data.insert(len(data),x) # 表新数据加到表的最后面,或写data.append(x)
时间复杂度为:
T(n) = O(1)+O(n)*(O(1)+O(1))
= O(n)
6.时间开销
- 构造新结构,如构造新的list、set等。
构造新的空结构(空表、空集合等)是常量时间操作,
而构造一个包含n个元素的结构,则至少需要O(n)时间。
- 一些list操作的效率:
表元素访问和元素修改是常量时间操作,
但一般的加入、删除元素操作(即使只加入一个元素)都是O(n)时间操作。
- 字典dict操作的效率:
主要操作是加入新的关键码-值对和基于关键码查找关联值。
它们的最坏情况复杂度是O(n),但平均复杂度是O(1)。
也就是说,一般而言字典操作的效率很高,但偶尔也会出现效率低的情况。
空间开销
- 在程序里使用任何类型的对象,都需要付出空间的代价。建立一个表或者元组,至少要占用元素个数那么多空间,即O(n)的存储空间,如果元素也是新创建的,还需要考虑元素本身的存储开销。
- 集合和字典需要支持快速查询等操作,其结构更加复杂。包含n个元素的集合或字典,至少需要占用O(n)的存储空间。
【注意】
python中的各种组合数据对象都没有预设的最大元素个数。在实际使用中,这些结构能根据元素个数的增长自动扩充存储空间。从空间占用的角度看,其实际开销在存续期间可能变大,但通常不会自动缩小(即使后来元素变得很少了)。举个例子,假设程序里建了个表,而后不断加入元素导致表变得很大,而后又不断删除元素,后来表中元素变得很少,但占用的存储空间并不减少。
还应该注意pyhton自动存储管理系统的影响。举个例子:如果在程序里建立了一个表,此后一直将其作为某个全局变量的值,这个对象就会始终存在并占用空间。如果将其作为函数里局部变量的值,或者虽然作为全局变量的值,但后来通过赋值将其抛弃,这个表对象就可以被回收。