时间复杂度的计算
概要说明
本文主要简单记录自己在学习数据结构的算法复杂度的过程中,遇到的问题和一些心得体会,便于以后自己参考。
时间复杂度的定义
一个语句的频度是指该语句在算法中被重复执行的次数。算法中所有语句的频度之和记为T(n),它是该算法问题规模 n 的函数,时间复杂度主要分析 T(n)
的数量级。算法中基本运算 (最深层循环内的语句)的频度与 T(n)同数量级,因此通常采用算法中基本运算的频度f(n))来分析算法的时间复杂度: T(n)=O(f(n))
加法规则
T ( n ) = T 1 ( n ) + T 2 ( n ) = O ( f ( n ) ) + O ( g ( n ) ) = O ( m a x ( f ( n ) , g ( n ) ) ) T(n) = T_1(n) + T_2(n) = O(f(n)) + O(g(n)) = O(max(f(n), g(n))) T(n)=T1(n)+T2(n)=O(f(n))+O(g(n))=O(max(f(n),g(n)))
乘法规则
T ( n ) = T 1 ( n ) × T 2 ( n ) = O ( f ( n ) ) × O ( g ( n ) ) = O ( f ( n ) × g ( n ) ) T(n) = T_1(n) \times T_2(n) = O(f(n)) \times O(g(n)) = O(f(n) \times g(n)) T(n)=T1(n)×T2(n)=O(f(n))×O(g(n))=O(f(n)×g(n))
常见的渐进时间复杂度
O ( 1 ) < O ( l o g ) 2 n < O ( n ) < O ( n l o g 2 n ) < O ( n 2 ) < O ( n 3 ) < O ( 2 n ) < O ( n ! ) < O ( n n ) O(1) < O(log)_2^n < O(n) < O(nlog_2^n) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n) O(1)<O(log)2n<O(n)<O(nlog2n)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
计算时间复杂度的2种通用方法
1 循环主体中的变量参与循环条件的判断
int i = 1;
while(i < n)
i=i*2;
假设第t次退出循环,每次循环i乘以2,第t次,就是 2 t 2^t 2t,那么有 2 t < n 2^t < n 2t<n,即是: t < = l o g 2 n t<= log_2^n t<=log2n,从而时间复杂度是 O ( l o g 2 n ) O(log_2^n) O(log2n)
2 循环主体的变量和循环条件无关
此类题可采用数学归纳法或直接累计循环次数。多层循环时从内到外分析,忽略单步语句、条件判断语句,只关注主体语句的执行次数。此类问题又可分为递归程序和非递归程序。递归程序一般使用公公式递推,非递归程序直接累计计数
例题 1
一个算法所需时间由下述递归方程表示,试求出该算法的时间复杂度的级别(或阶)
T
(
n
)
=
{
1
n
=
1
2
T
(
n
/
2
)
+
n
n
>
1
T(n) = \begin{cases} 1 & n = 1 \\ \\ 2T(n/2) + n & n > 1 \end{cases}
T(n)=⎩
⎨
⎧12T(n/2)+nn=1n>1
式中, n n n是问题的规模,为简单起见,设 n n n是2的整数次幕.
解答如下:
设
n
=
2
k
n=2^k
n=2k,其中
k
>
=
0
k>= 0
k>=0, 则有
T
(
2
k
)
=
{
1
k
=
0
2
T
(
2
k
−
1
)
+
2
k
k
>
0
T(2^k) = \begin{cases} 1 & k = 0 \\ \\ 2T(2^{k-1}) + 2^k & k > 0 \end{cases}
T(2k)=⎩
⎨
⎧12T(2k−1)+2kk=0k>0
从而,当
k
>
0
k>0
k>0时,
T
(
2
k
)
=
2
T
(
2
k
−
1
)
+
2
k
T(2^k) = 2T(2^{k-1}) + 2^k
T(2k)=2T(2k−1)+2k;用k取k-1得到:
T
(
2
k
−
1
)
=
2
T
(
2
k
−
2
)
+
2
k
−
1
T(2^{k-1}) = 2T(2^{k-2}) + 2^{k-1}
T(2k−1)=2T(2k−2)+2k−1
将
(
2
)
(2)
(2)式带入
(
1
)
(1)
(1)有:
T
(
2
k
)
=
2
(
2
T
(
2
k
−
2
)
+
2
k
−
1
)
+
2
k
=
2
×
2
T
(
2
k
−
2
)
+
2
×
2
k
−
1
+
2
k
=
2
×
2
T
(
2
k
−
2
)
+
2
×
2
k
=
2
2
×
T
(
2
k
−
2
)
+
2
×
2
k
\begin{align} T(2^k) & = \begin{aligned} & 2(2T(2^{k-2}) + 2^{k-1}) + 2^k \\ \end{aligned} \\ & = 2\times2T(2^{k-2}) + 2\times2^{k-1} + 2^k \\ & = 2\times2T(2^{k-2}) + 2\times2^k \\ & = 2^2\times T(2^{k-2}) + 2 \times2^k \end{align}
T(2k)=2(2T(2k−2)+2k−1)+2k=2×2T(2k−2)+2×2k−1+2k=2×2T(2k−2)+2×2k=22×T(2k−2)+2×2k
同样的,用k取k-2,并带入,得到:
T
(
2
k
)
=
2
(
2
T
(
2
k
−
2
)
+
2
k
−
1
)
+
2
k
=
2
×
2
T
(
2
k
−
2
)
+
2
×
2
k
−
1
+
2
k
=
2
×
2
T
(
2
k
−
2
)
+
2
×
2
k
=
2
2
×
T
(
2
k
−
2
)
+
2
×
2
k
=
2
2
×
(
2
T
(
2
k
−
2
+
2
k
−
2
)
)
+
2
k
=
2
3
×
T
(
2
k
−
3
)
+
2
2
×
2
k
−
2
+
2
k
=
2
3
×
T
(
2
k
−
3
)
+
3
×
2
k
\begin{align} T(2^k) & = \begin{aligned} & 2(2T(2^{k-2}) + 2^{k-1}) + 2^k \\ \end{aligned} \\ & = 2\times2T(2^{k-2}) + 2\times2^{k-1} + 2^k \\ & = 2\times2T(2^{k-2}) + 2\times2^k \\ & = 2^2\times T(2^{k-2}) + 2 \times2^k \\ & = 2^2 \times ( 2T(2^{k-2} + 2^{k-2}) ) + 2^k \\ & = 2^3 \times T(2^{k-3}) + 2^2 \times 2^{k-2} + 2^k \\ & = 2^3 \times T(2^{k-3}) + 3 \times 2^k \end{align}
T(2k)=2(2T(2k−2)+2k−1)+2k=2×2T(2k−2)+2×2k−1+2k=2×2T(2k−2)+2×2k=22×T(2k−2)+2×2k=22×(2T(2k−2+2k−2))+2k=23×T(2k−3)+22×2k−2+2k=23×T(2k−3)+3×2k
观察,猜测 T ( 2 k ) = 2 i T ( 2 k − i ) + i × 2 k T(2^k) = 2^iT(2^{k-i}) + i\times2^k T(2k)=2iT(2k−i)+i×2k,由于 T ( 1 ) = 1 T(1) = 1 T(1)=1,得到 , T ( 2 k ) = ( 1 + k ) × 2 k T(2^k) = (1+k) \times 2^k T(2k)=(1+k)×2k。用数学归纳法证明:
当k = 0时, T ( 1 ) = ( 1 + 0 ) × 2 0 = 1 T(1) = (1+0) \times 2^0 = 1 T(1)=(1+0)×20=1,成立,假设 k = m , m > 0 k=m, m> 0 k=m,m>0时,结论成立,则有: T ( 2 m ) = ( 1 + m ) × 2 m T(2^m) = (1+m) \times 2^m T(2m)=(1+m)×2m.
事实上, 当 k = m + 1 k=m+1 k=m+1时,有:
T
(
2
m
+
1
)
=
2
×
T
(
2
m
)
+
2
m
+
1
=
2
×
(
(
1
+
m
)
×
2
m
)
+
2
m
+
1
=
(
1
+
m
)
×
2
m
+
1
+
2
m
+
1
=
(
1
+
(
1
+
m
)
)
×
2
m
+
1
=
T
(
m
+
1
)
\begin{align} T(2^{m+1}) & = \begin{aligned} & 2 \times T(2^m) + 2^{m+1} \end{aligned} \\ & = 2 \times ( (1+m) \times 2^m ) + 2^{m+1} \\ & = (1+m) \times 2^{m+1} + 2^{m+1} \\ & = (1 + (1 +m)) \times 2^{m+1} \\ & = T(m+1) \end{align}
T(2m+1)=2×T(2m)+2m+1=2×((1+m)×2m)+2m+1=(1+m)×2m+1+2m+1=(1+(1+m))×2m+1=T(m+1)
从而猜测的结论成立. 将
k
=
l
o
g
2
n
k = log_2^n
k=log2n带入,得到:
T
(
n
)
=
{
1
n
=
1
n
+
n
l
o
g
2
n
n
>
1
T(n) = \begin{cases} 1 & n=1 \\ \\ n + nlog_2^n & n > 1 \end{cases}
T(n)=⎩
⎨
⎧1n+nlog2nn=1n>1
于是时间复杂度为:
O
(
n
)
+
O
(
n
l
o
g
2
n
)
O(n) +O(nlog_2^n)
O(n)+O(nlog2n) ,取二者较大值:
O
(
n
l
o
g
2
n
)
O(nlog_2^n)
O(nlog2n)
例题 2
求解斐波那契数列
F
(
n
)
=
{
1
n
=
0
,
1
F
(
n
−
1
)
+
F
(
n
−
2
)
n
>
1
F(n) = \begin{cases} 1 & n=0,1 \\ \\ F(n-1) + F(n-2) & n > 1 \end{cases}
F(n)=⎩
⎨
⎧1F(n−1)+F(n−2)n=0,1n>1
有2种常用的算法:递归算法和非递归算法。试分析这两各种算法的时间复杂度。
- 递归算法:
递归算法是最简单的方法,但它具有较高的时间复杂度,因为它会重复计算相同的子问题,导致指数级的递归调用。
递归算法的递归关系是:
F
(
n
)
=
F
(
n
−
1
)
+
F
(
n
−
2
)
F(n) = F(n-1) + F(n-2)
F(n)=F(n−1)+F(n−2)
递归算法的时间复杂度可用递归树来分析。每次递归调用都会分解成两个子问题,每个子问题又会分解成两个子问题,依此类推,直到 n 达到 0 或 1 时停止递归。因此,递归树的深度是 n,而每层的节点数是指数级增长的,约为 2 n 2^n 2n。
因此,递归算法的时间复杂度是指数级的,大约为 O ( 2 n ) O(2^n) O(2n)。这使得递归算法在计算较大的斐波那契数时非常低效。
- 非递归算法(迭代算法):
非递归算法采用迭代的方式来计算斐波那契数列,通过保存中间结果来避免重复计算,因此具有更低的时间复杂度。
非递归算法通过从小到大逐个计算斐波那契数,从 F ( 0 ) F(0) F(0) 和$ F(1) $开始,一直计算到 F ( n ) F(n) F(n)。这个过程只需要一次遍历,每次计算都只涉及前两个斐波那契数,因此时间复杂度是线性的,约为 O ( n ) O(n) O(n)。
总结:
- 递归算法的时间复杂度为 O ( 2 n ) O(2^n) O(2n),指数级增长,非常低效。
- 非递归算法的时间复杂度为 O ( n ) O(n) O(n),线性增长,效率更高。
因此,通常情况下,推荐使用非递归算法来计算斐波那契数列,特别是在需要计算大数值时,因为它具有更高的效率。递归算法只在教育和理论分析中常常使用,而在实际应用中通常不适用。
总结
计算一个程序的事件复杂度,主要从2方面下手,看循环变量影不影响循环,如果影响,那就从这里入手,直接计算程序的运行次数;如果不影响,那就涉及到递归,需要观察得到递归的通式,来得出程序的时间复杂度。
参考资料
王道 数据结构复习