递归算法的时间复杂度分析
一个认识递归问题的小例子
首先使用一个简单的案例看一下递归方程的时间复杂度分析。
假设有以下的简单算法:
int Sort(int i,int j)
{
if(i==j)
return(x[i]);
else
{
m = (i + j - 1)/2;
return(Merge(Sort(i,m),Sort(m + 1,j)));
}
}
x
x
x是一个数组,如果设输入的是
n
n
n, Sort在最坏的情况下的运行时间是
T
(
n
)
T(n)
T(n),那么
T
(
n
)
T(n)
T(n)可以写成如下的迭代式:
T
(
n
)
≤
{
c
1
,
n
=
1
2
T
(
n
2
)
+
c
2
n
,
n
>
1
T(n) \le \begin{cases} c_1,n=1\\ 2T(\frac{n}{2})+c_2n, n>1\end{cases}
T(n)≤{c1,n=12T(2n)+c2n,n>1
当
n
=
1
n=1
n=1时,算法的运行时间就是常数。而当
n
>
1
n>1
n>1时,算法的运行时间可以进一步细分为进一步进行两个Sort的时间,此时的
n
n
n已经变成了
n
2
\frac{n}{2}
2n。除此之外,还有判断
i
=
=
j
i==j
i==j和划分
m
m
m以及Merge函数带来的时间消耗,我们使用一个常数
c
2
n
c_2n
c2n来作为Sort中除递归以外其它时间开销的上界,至于使用
c
2
n
c_2n
c2n而不是
c
2
c_2
c2,是因为Merge函数的时间复杂度是正比于
n
n
n的(这一步是基于题目假设的,如果在其它的递归问题中,这个后缀项是可以变化的)。
至此,我已经给出了一个简单递归例子的递归方程,所有的递归问题都可以简化写成如此的一个方程,为了方便后续分析递归问题 ,我们可以给出这种迭代方程的通项公式:
一类递归方程的展开式通解
设 T ( n ) T(n) T(n)是某个问题的时间开销, n n n是该问题的规模,设对此问题列出的递归方程为:
T ( n ) = { 1 , n = 1 a T ( n / c ) + d ( n ) , n > 1 ( 1 ) T(n) = \begin{cases} 1,n=1\\ aT(n/c)+d(n), n>1\end{cases} (1) T(n)={1,n=1aT(n/c)+d(n),n>1(1)
其中 c c c是大于1的正整数。全部的数据被分割成 c c c等份,每份的数量为 n / c n/c n/c。 T ( n / c ) T(n/c) T(n/c)是求解一个子问题的时间开销。 a T ( n / c ) aT(n/c) aT(n/c)代表求解 a a a个子问题时间的开销。 d ( n ) d(n) d(n)是任意的函数,因此方程是严格的等式。
递归问题的时间分析
在上一节中,我们给出了递归方程的通项公式,但是并没有给出通项公式的解,下面是求解
T
(
n
)
T(n)
T(n)的详细步骤。
使用
n
/
c
i
n/c^i
n/ci代替式子(1)中的
n
n
n,得到:
T
(
n
c
i
)
=
a
T
(
n
c
i
+
1
)
+
d
(
n
c
i
)
(
2
)
T(\frac{n}{c^i}) = aT(\frac{n}{c^{i+1}}) + d(\frac{n}{c^i}) (2)
T(cin)=aT(ci+1n)+d(cin)(2)
由式(1)开始,将(2)代入(1)得到:
T
(
n
)
=
a
T
(
n
c
)
+
d
(
n
)
=
a
(
a
T
(
n
c
2
)
+
d
(
n
c
)
)
+
d
(
n
)
=
a
2
T
(
n
c
2
)
+
a
d
(
n
c
)
+
d
(
n
)
=
a
i
T
(
n
c
i
)
+
∑
j
=
0
i
−
1
a
j
d
(
n
c
j
)
\begin{aligned} T(n) &= aT(\frac{n}{c}) + d(n)\\ &= a(aT(\frac{n}{c^2})+d(\frac{n}{c}))+d(n) \\ &=a^2T(\frac{n}{c^2}) + ad(\frac{n}{c}) + d(n)\\ &=a^iT(\frac{n}{c^i}) + \sum_{j=0}^{i-1}a^jd(\frac{n}{c^j}) \end{aligned}
T(n)=aT(cn)+d(n)=a(aT(c2n)+d(cn))+d(n)=a2T(c2n)+ad(cn)+d(n)=aiT(cin)+j=0∑i−1ajd(cjn)
假设
n
=
c
k
n=c^k
n=ck,则
T
(
n
/
c
k
)
=
T
(
1
)
=
1
T(n/c^k) = T(1) = 1
T(n/ck)=T(1)=1。令
i
=
k
i = k
i=k,有:
T
(
n
)
=
a
k
+
∑
j
=
0
k
−
1
a
j
d
(
c
k
−
j
)
(
3
)
T(n) = a^k + \sum_{j = 0}^{k-1}a^jd(c^{k-j})(3)
T(n)=ak+j=0∑k−1ajd(ck−j)(3)
由
n
=
c
k
n=c^k
n=ck可以推出
k
=
l
o
g
c
n
k = log_cn
k=logcn,则上式的第一项可以写成
a
l
o
g
c
n
a^{log_cn}
alogcn,同时也可以写成
n
l
o
g
c
a
n^{log_ca}
nlogca。下面给出了这个等价的证明
证明 n l o g c a n^{log_ca} nlogca = a l o g c n a^{log_cn} alogcn
设 t = l o g c a t = log_ca t=logca,则 c t = a c^t = a ct=a,左边等于 n t n^t nt,右边是 ( c t ) l o g c n = c t l o g c n = c l o g c n t = n t (c^t)^{log_cn} = c^{t log_cn} = c^{log_cn^t} = n^t (ct)logcn=ctlogcn=clogcnt=nt,所以左边等于右边。
上式的 n l o g c a 是 n^{log_ca}是 nlogca是 d ( n ) d(n) d(n)恒为0时的解,称为齐次解。齐次解代表求解所有子问题的时间开销,而第二项称为特解,特解主要是对问题数据的分割和处理,以及建立子问题所消耗的时间,还包括合并所有子问题求解结果等所花费的时间。
针对一个递归问题,我们要做的就是计算(3)式的时间复杂度,第一项是 n l o g c a n^{log_ca} nlogca,第二项是我们需要考虑的,由于第二项存在一个抽象函数 d ( n ) d(n) d(n),因此我们需要对 d ( n ) d(n) d(n)的情况进行分类讨论,从而确定递归函数的通解情况。
递归函数特解情况分类讨论
倍积函数
如果
d
(
n
)
d(n)
d(n)满足倍积函数性质,则有对所有正整数
x
,
y
x,y
x,y,有
f
(
x
y
)
=
f
(
x
)
f
(
y
)
f(xy) = f(x)f(y)
f(xy)=f(x)f(y),就是说
f
f
f是正整数上的倍积函数。在上面的式子(3)中,现预定
d
(
n
)
d(n)
d(n)是倍积函数,因此满足
d
(
c
k
−
j
)
=
(
d
(
c
)
)
k
−
j
d(c^{k-j}) = (d(c))^{k-j}
d(ck−j)=(d(c))k−j。
由此(3)式中的特解可以写为:
∑
j
=
0
k
−
1
a
j
d
(
c
k
−
j
)
=
∑
j
=
0
k
−
1
a
j
(
d
(
c
)
)
k
−
j
=
(
d
(
c
)
)
k
∑
j
=
0
k
−
1
(
a
d
(
c
)
)
j
=
a
k
−
(
d
(
c
)
)
k
a
d
(
c
)
−
1
(
4
)
\begin{aligned} \sum_{j = 0}^{k-1}a^jd(c^{k-j})& = \sum_{j = 0}^{k-1}a^j(d(c))^{k-j}\\ &= (d(c))^k\sum_{j = 0}^{k-1}(\frac{a}{d(c)})^j \\ &=\frac{a^k-(d(c))^k}{\frac{a}{d(c)}-1}(4) \end{aligned}
j=0∑k−1ajd(ck−j)=j=0∑k−1aj(d(c))k−j=(d(c))kj=0∑k−1(d(c)a)j=d(c)a−1ak−(d(c))k(4)
到此可以看出规律,我们可以给出下面的定理:
设 a 、 c a、c a、c是非负常数, n n n是2的幂, d ( n ) d(n) d(n)是倍积函数,则
T ( n ) = { b , n = 1 a T ( n / c ) + d ( n ) , n > 1 ( 1 ) T(n) = \begin{cases} b,n=1\\ aT(n/c)+d(n), n>1\end{cases} (1) T(n)={b,n=1aT(n/c)+d(n),n>1(1)
的齐次解是 O ( n l o g c a ) O(n^{log_ca}) O(nlogca),且对特解有如下结论(设 k = l o g c n k = log_cn k=logcn)。
一、若 a > d ( c ) a>d(c) a>d(c),则特解是 O ( a k ) O(a^k) O(ak)或 O ( n l o g c a ) O(n^{log_ca}) O(nlogca),即特解与齐次解同阶。
二、若 a < d ( c ) a<d(c) a<d(c),则特解是 O ( ( d ( c ) ) k ) O((d(c))^k) O((d(c))k)或 O ( n l o g c d ( c ) ) O(n^{log_cd(c)}) O(nlogcd(c)),即特解与 d d d同阶。
三、若 a = d ( c ) a = d(c) a=d(c), 则特解是其次解的 l o g c n log_cn logcn倍。
上述三个结论的证明很简单,前两个只需要对(4)式进行分析即可,若
a
>
d
(
c
)
a>d(c)
a>d(c),那么按照计算时间复杂度来说特解是
O
(
a
k
)
O(a^k)
O(ak),即与齐次解相同,那么此时的递归算法的整体时间复杂度是
O
(
a
k
)
O(a^k)
O(ak)即
O
(
n
l
o
g
c
a
)
O(n^{log_ca})
O(nlogca)。
对于三、我们不能从(4)中分析,可以这么分析:
∑ j = 0 k − 1 a j ( d ( c ) ) k − j = ( d ( c ) ) k ∑ j = 0 k − 1 ( a d ( c ) ) j = ( d ( c ) ) k ∑ j = 0 k − 1 1 = ( d ( c ) ) k × k = ( d ( c ) ) l o g c n × l o g c n = n l o g c d ( c ) × l o g c n \sum_{j = 0}^{k - 1}a^j(d(c))^{k-j} = (d(c))^k\sum_{j = 0}^{k-1}(\frac{a}{d(c)})^j = (d(c))^k\sum_{j = 0}^{k - 1}1 = (d(c))^k×k = (d(c))^{log_cn}×log_cn = n^{log_cd(c)}×log_cn ∑j=0k−1aj(d(c))k−j=(d(c))k∑j=0k−1(d(c)a)j=(d(c))k∑j=0k−11=(d(c))k×k=(d(c))logcn×logcn=nlogcd(c)×logcn
因此对于不同的参数
a
a
a与
d
(
c
)
d(c)
d(c),我们能分类讨论。
下面针对倍积函数,列举了几个例子。
设有三个方程。当 n ≥ 2 n\ge 2 n≥2时,它们分别满足下面的式子,当 n = 1 n = 1 n=1时, T ( 1 ) = 1 T(1) = 1 T(1)=1。
1、 T ( n ) = 4 T ( n / 2 ) + n T(n) = 4T(n/2) + n T(n)=4T(n/2)+n
2、 T ( n ) = 4 T ( n / 2 ) + n 2 T(n) = 4T(n/2) + n^2 T(n)=4T(n/2)+n2
3、 T ( n ) = 4 T ( n / 2 ) + n 3 T(n) = 4T(n/2) + n^3 T(n)=4T(n/2)+n3
由于 a a a和 c c c的值是不变的, a = 4 a = 4 a=4, c = 2 c = 2 c=2.因此只需要分析 d ( n ) d(n) d(n)即可。
齐次解 n l o g c a = n 2 n^{log_ca} = n^2 nlogca=n2。
对1来说, d ( n ) = n d(n) = n d(n)=n, a > d ( c ) a>d(c) a>d(c),特解与齐次解同阶,因此 T ( n ) = O ( n 2 ) T(n) = O(n^2) T(n)=O(n2).
对2来说, d ( n ) = n 2 d(n) = n^2 d(n)=n2, a = d ( c ) a = d(c) a=d(c),特解是齐次解的 l o g c n log_cn logcn倍,因此 T ( n ) = O ( n 2 l o g n ) T(n) = O(n^2logn) T(n)=O(n2logn).
对3来说, d ( n ) = n 3 d(n) = n^3 d(n)=n3, a < d ( c ) a < d(c) a<d(c),特解与 d d d同阶,因此 T ( n ) = O ( n 3 ) T(n) = O(n^3) T(n)=O(n3).
倍积函数与常数之积
当
d
(
n
)
d(n)
d(n)是常数与倍积函数之积时,我们可以通过转化的方法将其化简为倍积的情况,下面是一个例子。
设有
T
(
n
)
=
{
1
,
n
=
1
3
T
(
n
2
)
+
2
n
1.5
,
n
>
1
T(n) = \begin{cases} 1,n=1\\ 3T(\frac{n}{2})+2n^{1.5}, n>1\end{cases}
T(n)={1,n=13T(2n)+2n1.5,n>1
2
n
1.5
2n^{1.5}
2n1.5不是倍积函数,但是
n
1.5
n^{1.5}
n1.5却是。我们令
U
(
n
)
=
T
(
n
)
/
2
U(n) = T(n)/2
U(n)=T(n)/2,则得到
{
U
(
1
)
=
1
/
2
U
(
n
)
=
3
U
(
n
/
2
)
+
n
1.5
\begin{cases} U(1) = 1/2\\ U(n) = 3U(n/2) + n^{1.5}\end{cases}
{U(1)=1/2U(n)=3U(n/2)+n1.5
其齐次解是
n
l
o
g
2
3
=
n
1.59
n^{log_23} = n^{1.59}
nlog23=n1.59,所以
T
(
n
)
T(n)
T(n)的齐次解是
n
1.59
/
2
n^{1.59}/2
n1.59/2,仍然是
O
(
n
1.59
)
O(n^{1.59})
O(n1.59)。
对于特解来说,由于
a
=
3
,
c
=
2
,
d
(
n
)
=
n
1.5
,
d
(
c
)
=
c
1.5
=
2.82
<
a
a = 3,c = 2,d(n) = n^{1.5},d(c) = c^{1.5} = 2.82<a
a=3,c=2,d(n)=n1.5,d(c)=c1.5=2.82<a,所以关于
U
(
n
)
U(n)
U(n)的特解也是
O
(
n
1.59
)
O(n^{1.59})
O(n1.59)。最终
T
(
n
)
T(n)
T(n)也是
O
(
n
1.59
)
O(n^{1.59})
O(n1.59)。
从这个例子中能看出,对于常数与倍积函数相乘的情况,我们可以先进行化简,使得其成为倍积函数的情况。
其它非倍积函数
当
d
(
n
)
d(n)
d(n)为非倍积函数又无法进行变化时,只得对
∑
j
=
0
k
−
1
a
j
d
(
c
k
−
j
)
\sum_{j = 0}^{k-1}a^jd(c^{k-j})
j=0∑k−1ajd(ck−j)进行运算,进而求出特解得紧凑上界。下面也是一个例子。
设有:
T
(
n
)
=
{
1
,
n
=
1
2
T
(
n
2
)
+
n
l
o
g
n
,
n
>
1
T(n) = \begin{cases} 1,n=1\\ 2T(\frac{n}{2})+nlogn, n>1\end{cases}
T(n)={1,n=12T(2n)+nlogn,n>1
由
a
=
c
=
2
a = c = 2
a=c=2,易知齐次解是
n
l
o
g
c
a
=
n
n^{log_ca}= n
nlogca=n,但是
d
(
n
)
=
n
l
o
g
n
d(n) = nlogn
d(n)=nlogn并不是倍积函数,无法使用定理来处理,因此需要其它的方法。
我们可以直接对级数求和,有
∑
j
=
0
k
−
1
a
j
d
(
c
k
−
j
)
=
∑
j
=
0
k
−
1
2
j
×
2
k
−
j
×
l
o
g
2
k
−
j
=
2
k
∑
j
=
0
k
−
1
(
k
−
j
)
=
2
k
−
1
×
k
(
k
+
1
)
\begin{aligned} \sum_{j = 0}^{k-1}a^jd(c^{k-j})& = \sum_{j= 0}^{k-1}2^j ×2^{k-j}×log{2^{k-j}} \\&= 2^k\sum_{j = 0}^{k-1}(k-j)\\ & = 2^{k - 1} ×k(k+1)\end{aligned}
j=0∑k−1ajd(ck−j)=j=0∑k−12j×2k−j×log2k−j=2kj=0∑k−1(k−j)=2k−1×k(k+1)
由于我们前面令的 k = l o g n k = logn k=logn,因此特解的时间是 O ( n l o g 2 n ) O(nlog2^n) O(nlog2n),特解是大于齐次解的,因此特解是 T ( n ) T(n) T(n)的最终的时间复杂度。(这里为什么特解的时间是 O ( n l o g 2 n ) O(nlog2^n) O(nlog2n)我并没有验算,应该就是把 k k k代入到最终的化简式中分析)。
总结
到这里所有有关递归算法的简单分析都已经结束了,针对一个递归问题,首先可以将其使用数学递推式表示出来,根据后缀项的情况进行分类讨论,可以分为倍积、倍积常数和其它。
当
d
(
n
)
d(n)
d(n)是倍积项时,不需要进行计算,直接使用定理即可,通过简单的比较
a
与
d
(
c
)
a与d(c)
a与d(c)的大小关系来确定最终
T
(
n
)
T(n)
T(n)的时间复杂度。当
a
>
d
(
c
)
a>d(c)
a>d(c)时,特解与齐次解同阶;当
a
=
d
(
c
)
a = d(c)
a=d(c)时,特解是齐次解的
l
o
g
c
n
log_cn
logcn倍;当
a
<
d
(
c
)
a<d(c)
a<d(c)时,特解与
d
d
d同阶。齐次解是
n
l
o
g
c
a
n^{log_ca}
nlogca。
当
d
(
n
)
d(n)
d(n)是倍积乘常数时,需要先进行化简,化简为能使用定理处理的情况。当
d
(
n
)
d(n)
d(n)是其它情况时,可以直接代入级数项求和,根据求出的结果和齐次解进行比较从而再确定。