时间复杂度
- 算法的时间量度
一个算法是由控制结构(顺序,分支,循环)和原操作(固有数据类型的操作)构成的。算法时间取决于二者的综合效果。
为了便于比较同一问题的不同算法,通常将作为问题中基本操作的源操作重复执行的次数作为算法的时间量度。
- 时间复杂度
一般情况下,算法中基本操作重复执行的次数是问题规模
n
n
n的某个函数
f
(
n
)
f(n)
f(n),那么算法上的时间量度
T
T
T记作
T
(
n
)
=
O
(
f
(
n
)
)
T(n)=O(f(n))
T(n)=O(f(n))
它表示随问题规模
n
n
n的增大,算法执行时间的增长率和
f
(
n
)
f(n)
f(n)的增长率相同,称作算法的渐进时间复杂度,简称时间复杂度。
时间复杂度用来度量算法效率。
基本操作的重复执行次数和算法的执行时间成正比,执行次数和包含它的语句频度相同。
语句中的频度是指该语句重复执行的次数。
注意,基本操作不一定只是一条语句,它可以是多条语句,
为了方便理解,给出几个示例分析一下:
{++x;}
for(int i = 0; i < n;i++){++x;}
for(int i = 0; i < n;i++)
for(int j = 0; j < n;j++){++x;}
比较这三块代码,作为基本操作的“++x”分别执行了 1 1 1、 n n n、 n 2 n^2 n2次,那么相应的,包含它们的语句的频度就是 1 1 1、 n n n、 n 2 n^2 n2,则它们的时间复杂度分别是 O ( 1 ) O(1) O(1)、 O ( n ) O(n) O(n)、 O ( n 2 ) O(n^2) O(n2),分别是常量阶,线性阶和平方阶。除此之外,还有对数阶 O ( log n ) O(\log n) O(logn),指数阶 O ( 2 n ) O(2^n) O(2n)等,应该尽量选用多项式阶 O ( n k ) O(n^k) O(nk)的算法,避免选用指数阶的算法(效率低)。
由于算法的时间复杂度考虑的只是对于问题规模 n n n的增长率,实际中必然存在语句频度(执行次数)难以精确计算的情况,此时只需要求出它关于 n n n的增长率或阶即可。比如:
for(int i = 2;i <= n;i++)
for(int j = 2;j <= i-1;j++){++x;}
对于语句++x
的频度表达式是
(
n
−
1
)
(
n
−
2
2
)
\frac {(n-1)(n-2}{2})
2(n−1)(n−2)。那么执行次数关于
n
n
n的增长率就是是
n
2
n^2
n2,它是频度表达式中增长最快的项。我们把
O
(
n
2
)
O(n^2)
O(n2)作为这个算法的时间复杂度。
- 最坏情况时间复杂度
有的情况下,算法中的基本操作的重复次数还和问题的输入数据集有关系,考虑一下冒泡排序的算法,我们的程序接收的数据集如果已经是从小到大排好的,那么将交换元素作为基本操作的重复次数就为0,就此时就是最好情况。那么只需要考虑其他的语句执行情况(元素间的比较),比较n-1次,此时对应的就是最好情况下的时间复杂度是 O ( n ) O(n) O(n)。如果是从大到小排好的,此时就是最坏情况:比较 n ( n − 1 ) 2 \frac {n(n-1)}{2} 2n(n−1)次,交换 n ( n − 1 ) 2 \frac {n(n-1)}{2} 2n(n−1)次,此时对应的就是最差情况下的时间复杂度是 O ( n 2 ) O(n^2) O(n2)。如果考虑它对所有可能的输入数据集的期望值,取平均时间复杂度 O ( n 2 ) O(n^2) O(n2)。
除特别指明外,一般我们说的时间复杂度都是指最坏情况下的时间复杂度。
空间复杂度
类似时间复杂度,空间复杂度是算法所需存储空间的量度,记作
S
(
n
)
=
O
(
f
(
n
)
)
S(n)=O(f(n))
S(n)=O(f(n))
n
n
n仍然是问题规模的大小。
程序除了需要存储空间来寄存本身所用的指令、常数、变量和输入数据外,也需要一些对数据进行操作的工作单元和存储一些为实现算法所需信息的辅助空间。
如果输入数据所占空间只取决于问题本身,和算法无关,则只需要分析出输入和程序之外的额外空间,否则应同时考虑输入本身所需空间(和输入数据的表达形式有关)。
若额外空间相对于输入数据量来说是常数,则称次算法为原地工作,排序算法大都是这类。若所占空间量依赖于特定的输入,则除特定的输入外,均按最坏的情况分析。
算法的空间复杂度一般以数量级的形式给出。如:
当一个算法的空间复杂度为一个常量,即不随被处理数据量n的大小而改变时,可表示为O(1);
当一个算法的空间复杂度与以2为底的n的对数成正比时,可表示为O(log2n);
当一个算法的空间复杂度与n成线性比例关系时,可表示为O(n)。
若形参为数组,则只需要为它分配一个存储由实参传送来的一个地址指针的空间,即一个机器字长空间;若形参为引用方式,则也只需要为其分配存储一个地址的空间,用它来存储对应实参变量的地址,以便由系统自动引用实参变量。