复杂性概念
空间复杂度:指令空间、数据空间和环境栈空间(递归)
时间复杂度:由关键操作计数决定,一般将加减乘除和比较操作看作是基本操作(约定每个基本操作所用时间都是一个单位),然后确定程序总的执行步数(不能用机器的真正运行时间作衡量标准,因为影响运行时间的因素太多,甚至还有很多未知因素)。
复杂性举例
//寻找a[0:n-1]中的最大元素
template<class T>
int Max(T a[], int n)
{
int pos=0;
for (int i=1; i<n; i++)
if (a[pos]<a[i])
pos=i;
return pos;
}
关键操作是比较,一共进行了n-1次比较,所用时间复杂对为T(n)=n-1步。
根据不同的程序与算法,关键操作数又分为最坏情况,最好情况以及平均情况(设每种情况概率相等并求数学期望),如下:
//在a[0:n-1]中搜索x,若找到则返回所在的位置,否则返回-1
template < class T >
int SeqSearch (T a[ ], const T &x, int n)
{
int i;
for (i=0; i<n && a[i]!= x; i++);
if (i==n) return -1;
return i;
}
最好情况:比较 1 次;
最坏情况:比较 n 次;
平均情况:
1
n
+
1
(
n
+
∑
i
=
1
n
i
)
=
n
2
+
n
n
+
1
{1\over n+1}(n+\sum\limits_{i=1}^ni)={n\over 2}+{n\over n+1}
n+11(n+i=1∑ni)=2n+n+1n
(能找到的有n种情况,遍历完找不到为一种情况,总共n+1种情况,每种概率为
1
n
+
1
1\over n+1
n+11)
引入渐进符号
- 时间复杂度的分析当输入规模n很大时才有意义,n趋向于无穷
- 输入规模很大时,只关心T(n)的阶,更加方便
常用的渐进函数:1,logn,n,nlogn,n2,2n,n!
渐近上界:
f
(
n
)
=
O
(
g
(
n
)
)
f(n) = O(g(n))
f(n)=O(g(n)),存在正实数 c 和正整数 N, 使得当n>N时, f(n) ≤ c·g(n),重要的上界:
n
2
n
=
O
(
n
!
)
n2^n=O(n!)
n2n=O(n!)
渐近下界:
f
(
n
)
=
Ω
(
g
(
n
)
)
f(n) = Ω(g(n))
f(n)=Ω(g(n)),存在正实数 c 和正整数 N, 使得当n>N时,f (n) ≥ c·g(n)
渐近同阶:
f
(
n
)
=
Θ
(
g
(
n
)
)
f(n) =Θ(g(n))
f(n)=Θ(g(n)),函数g(n)既是f (n)的渐近上界又是f (n)的渐近下界。此时如果极限
l
i
m
(
f
(
n
)
g
(
n
)
)
lim({f(n)\over g(n)})
lim(g(n)f(n))存在的话,应该是一个正实数。
f ( n ) f(n) f(n) | g ( n ) g(n) g(n) | f ( n ) = O ( g ( n ) ) f(n)=O(g(n)) f(n)=O(g(n)) | f ( n ) = Ω ( g ( n ) ) f(n)=Ω(g(n)) f(n)=Ω(g(n)) | f ( n ) = Θ ( g ( n ) ) f(n)=Θ(g(n)) f(n)=Θ(g(n)) |
---|---|---|---|---|
2n3+3n | 100n2+2n+100 | F | T | F |
50n+logn | 10n+loglogn | T | T | T |
50nlogn | 10nloglogn | F | T | F |
logn | log2n | T | F | F |
n! | 5n | F | T | F |
举例(只保留主项的阶):
关键操作数为:n-1,则T(n)=n
2(n-1),则T(n)=n
按照渐进阶从低到高的顺序排列以下表达式:
4
n
2
,
l
o
g
n
,
3
n
,
20
n
,
n
2
/
3
,
n
!
4n^2,logn,3^n,20n,n^{2/3},n!
4n2,logn,3n,20n,n2/3,n!
l
o
g
n
<
n
2
/
3
<
20
n
<
4
n
2
<
3
n
<
n
!
logn < n^{2/3} < 20n < 4n^2 < 3^n < n!
logn<n2/3<20n<4n2<3n<n!
(logn永远小于nx,阶乘最大且n2n=O(n!),指数的上界是阶乘)
实例分析
1)假设某算法在输入规模是 n 时为T(n) = 3 ∗ 2 n 3*2^n 3∗2n。 在某台计算机上实现并完成该算法的时间是t 秒.现有另一台计算机,其运行速度为第一台的 64 倍, 那么,在这台计算机上用同一算法在t 秒内能解决规模为多大的问题?
设原机器速度为(v步关键步数/s),则现在机器速度为64v,输入规模为n时,机器执行 3 ∗ 2 n 3*2^n 3∗2n 步关键步数,用的时间为t,则有 3 ∗ 2 n v 3*2^n\over v v3∗2n=t ,在新机器上则为, 3 ∗ 2 m 64 v 3*2^m\over 64v 64v3∗2m=t ,连立俩个公式得,m=n+6,所以输入规模增大为 n+6。
2)若上述算法改进后的新算法的时间复杂度为 T(n) = n 2 n^2 n2 , 则在新机器上用t 秒时间能解决输入规模为多大的问题?
设输入规模为m,则时间复杂度为 T(m)= m 2 m^2 m2,新机器速度为64v,则 m 2 64 v m^2\over 64v 64vm2=t,又由上面得 3 ∗ 2 n v 3*2^n\over v v3∗2n=t,连立得 m=8 3 ∗ 2 n \sqrt{3*2^n} 3∗2n
3)若进一步改进算法,最新的算法的时间复杂度为T(n) = 8,其余条件不变,在新机器上运行,在t 秒内能够解决输入规模为多大的问题?
输入规模可以是任意大,因为无论输入多少,程序就运行8步关键步数,不随输入规模增大而增大。
复杂性了解
由于当前设备越来越廉价,使得空间复杂度变得不那么重要,有时工程上为保证响应时间,常常是拿空间来换时间,所以对于时间复杂度的要求更重要。对于一个算法或问题明确其时间复杂度尤为重要,计算效率上甚至是可不可解都需要去明确,如指数时间复杂度问题,如果问题规模变大,则问题会变得不可解。
如著名的旅行商问题(NP-hard),我们用时间复杂度来直观的阐释,设城市数量为n,旅行商问题的状态空间搜索树为一颗排列树,所以运算量的上限为O(n!),由于O(n!)=O(n×
2
n
2^n
2n),如果城市数量为100个,则100×
2
100
2^{100}
2100≈1.8 ×
1
0
32
10^{32}
1032,我们用10亿次/秒的超级计算机来求解,
1.8
×
1
0
32
1
0
9
1.8 ×10^{32}\over 10^9
1091.8×1032=1.8 ×
1
0
23
10^{23}
1023秒=5千7百万亿年,计算机运行一天也就只能解决41个城市。
相对于多项式复杂度算法:快速排序
(
O
(
n
l
o
g
n
)
)
(O(nlogn))
(O(nlogn)) 10万个数据1.7毫秒就处理完,网络中的Dijkstra算法(O(
n
2
n^2
n2))求解1万个顶点的单源最短路径问题0.1秒处理完,计算机是不怕O(
n
x
n^x
nx)复杂度,但对于无多项式算法的指数型问题是束手无策的,NP完全性理论就是用来揭示问题的难易程度。