评价算法优劣的依据:复杂度(时间复杂度和空间复杂度)
算法的复杂度体现在运行该算法时的计算机所需的资源的多少上,计算机资源最重要的是时间和空间资源,因此复杂度分为时间和空间复杂度。
时间复杂度是执行算法所需要的计算工作量。
空间复杂度是执行这个算法所需要的内存空间。
时间复杂度(Time Complexity)定义
- 时间频度
一个算法执行所耗的时间,从理论上是不能算出来的,必须上机运行测试才知道。
但是我们不可能也没有必要对每个算法上机测试。
一个算法花费的时间与算法语句的执行次数成正比例,那个算法中语句执行次数多,他花费的时间就多。
一个算法中语句执行次数称为语句频度,表为T(n),n表示问题的规模。 - 时间复杂度
但有时我们像知道它变化时呈现什么规律,想知道问题的规模,而不是具体的次数,此时引入时间复杂度。
一般情况下,算法中基本操作重复执行次数是问题规模n的某个函数,则称为f(n)是T(n)的同数量级函数。
记作T(n)=Of(n),称Of(n)为算法的渐进时间复杂度,简称时间复杂度。或者说时间复杂度就是时间频度去掉低阶项和首项常数。
注意:时间频度与时间复杂度是不同的,时间频度不同但是时间复杂度可能相同。
比如:某两个算法的时间频度是T(n)= 100000 n 2 + 10 n + 6 100000n^2+10n+6 100000n2+10n+6 T(n)= 10 n 2 + 10 n + 6 10n^2+10n+6 10n2+10n+6但是时间复杂度都是 T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2) - 最坏时间复杂度和平均时间复杂度
最坏情况下的时间复杂度称为最坏时间复杂度。一般不特别说明,讨论的时间复杂度均是最坏情况下的时间复杂度。
这样做的原因是:最坏情况下的时间复杂度是算法在任意输入实例上运行的上界,这就保证了算法的运行时间不会比任何更长。
在最坏情况下的时间复杂度为T(n)=O(n),它表示对于任意输入实例,该算法的运行时间不可能大于O(n)。
平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,算法的期望运行时间。鉴于平均复杂度。
第一,难计算
第二,又很多算法的平均情况和最差情况的复杂度是一样的。
所以一般讨论最坏复杂度。
为 进 一 步 说 明 算 法 的 时 间 复 杂 度 , 定 义 O 、 Ω 、 Θ 符 号 为进一步说明算法的时间复杂度,定义O、\Omega、\Theta符号 为进一步说明算法的时间复杂度,定义O、Ω、Θ符号 - O(欧几可容)符号给出了算法时间复杂度的上界(最坏情况<=),比如 T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2)
- Ω \Omega Ω(欧米伽)符号给出了是时间复杂度的下界(最好情况>=),比如 T ( n ) = Ω ( n 2 ) T(n)=\Omega(n^2) T(n)=Ω(n2)
- Θ \Theta Θ(西塔)给出了算法时间复杂度的精确阶(最好和最坏是同一个阶) T ( n ) = Θ ( n 2 ) T(n)=\Theta(n^2) T(n)=Θ(n2)
时间复杂度计算
根本没有必要计算时间频度,即使计算处理还要忽略常量、低次幂和最高次幂的系数,所以可以采用如下简单方法:
- 找出算法中的基本语句;
算法中执行次数最多的那条语句就是基本语句,通常是最内层的循环体。 - 计算基本语句的执行次数的数量级;
只需计算及基本语句执行次数的数量级,这就意味着只要保证基本语句执行次数的函数中的最高次幂正确即可。
可以忽略所有低次幂和最高次幂的系数,这样能够简化算法分析,并且使注意力集中在最重要的一点上:增长率。 - 用O表示算法的时间性能。
将基本语句执行的次数的数量级放入O中。
时间复杂度的几条基本计算原则
- 基本操作:即只有常数项,认为其时间复杂度为O(1)
- 顺序在操作:时间复杂度按加法及逆行计算
- 循环操作:时间复杂度按乘法进行计算
- 分支结构:时间复杂度取最大值
- 判断一个算法的效率时,往往只需要关注数量的最高次项,其他次要项和常数项可以忽略
- 在没有特殊说明时,我们所分析的算法的时间复杂度是指最坏时间复杂度
例如:
for a in range(0,1001):
for b in range(0,1001):
c = 1000 - a -b
if a**2 + b**2 == c**2:
print(a,b,c)
T(n)=nn(1+max(1,0))
算法时间复杂度举例
- 一个简单语句的时间复杂度为O(1)。
int count=0
- 100个简单语句的时间复杂度也是O(1)。(100是常数,不是趋向无穷大的n)
int count=0
- 一个循环的时间复杂度O(n)。
int n=8,count=0;
for (int i=1;i<=n;i++)
count++;
- 时间复杂度为O( log 2 n \log_2 n log2n)
int n=8,count=0;
for (int i=1;i<=n;i*=2)
count++;
1 2 4 8 16 32 …
n
=
2
i
n=2^i
n=2i 所以:
i
=
log
2
n
i=\log_2 n
i=log2n
- 时间复杂度为O( n 2 n^2 n2)
int n=8,count=0;
for (int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
count++;
- 时间复杂度为O(n / l o g 2 n /log_2 n /log2n)的二重循环:
int n=8,count=0;
for (int i=1;i<=n;i*=2)
for(int j=1;j<=n;j++)
count++;
- 时间复杂度为O( n 2 n^2 n2)
int n=8,count=0;
for (int i=1;i<=10n;i++)
for(int j=1;j<=100n;j++)
count++;
- 时间复杂度为O( n 2 n^2 n2)
int n=8,count=0;
for (int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
count++;
1+2+3+…+n=(1+n)*n/2
查找和排序算法会大量的设计时间复杂度,作为选择查找和排序算法的重要依据。
常用的时间复杂度级别
越往下,时间复杂度越高,执行效率越来越低:
常数阶O(1)
对数阶
O
(
log
2
n
)
O(\log_2 n)
O(log2n)
线性阶O(n)
线性对数阶
O
(
n
log
2
n
)
O(n\log_2 n)
O(nlog2n)
平方阶
O
(
n
2
)
O(n^2)
O(n2)
立方阶
O
(
n
3
)
O(n^3)
O(n3)
…
k次方阶
O
(
n
k
)
O(n^k)
O(nk)
指数阶
O
(
2
n
)
O(2^n)
O(2n)
阶乘阶
O
(
n
!
)
O(n!)
O(n!)
O
(
1
)
<
O
(
l
o
g
n
)
<
O
(
n
)
<
O
(
n
l
o
g
n
)
<
O
(
n
2
)
<
O
(
n
3
)
<
O
(
2
n
)
<
O
(
n
!
)
<
O
(
n
n
)
O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)
O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
时间复杂度 | n=8( 2 3 2^3 23) | n=10 | n=100 | n=1000 |
---|---|---|---|---|
O(1) | 1 | 1 | 1 | 1 |
O ( log 2 n ) O(\log_2 n) O(log2n) | 3 | 3.322 | 6.644 | 9.966 |
O ( n ) O(n) O(n) | 8 | 10 | 100 | 1000 |
O ( n log 2 n ) O(n\log_2 n) O(nlog2n) | 24 | 33.22 | 664.4 | 9966 |
O ( n 2 ) O(n^2) O(n2) | 64 | 100 | 10000 | 1 0 6 10^6 106 |
O ( log 2 n ) O(\log_2 n) O(log2n)和| O ( n ) O(n) O(n)差异很大,因此在编写算法时一定要注意时间复杂度的选择。