分治策略时间复杂度分析(二)-用递归树方法求解递归式
虽然可以用上一篇文章中的代入法去简洁地证明一个解确实是递归式的正确解,但是想出一个好的猜测可能会很困难。所以我们可以用递归树的方法去猜测解。
前言
进行分治策略时间复杂度分析有三种方法,分别为
- 1.用代入法求解递归式
- 2.用递归树方法求解递归式
- 3.用主方法求解递归式
本篇文章介绍第二种方法,即用递归树方法来求解递归式,在递归树当中,每个结点表示一个单一子问题的代价,子问题对应某次递归函数的调用。我们将树中每层中的代价求和,得到每层代价,然后将所有层的代价求和,得到所有层次的递归调用的总代价。我会通过两个例子去仔细地剖析递归树方法的使用。
一、递归树法初探
递归树最适合用来生成好的猜测,然后即可以用代入法去验证猜测是否正确。当使用递归树来生成好的猜测的时候,常常需要忍受一点儿“不精确”,因为稍后才会验证猜测是否正确。但是如果在画递归树的时和代价求和时非常仔细,就可以用递归树直接证明解是否正确。
二、第一个简单例子
以递归式
T
(
n
)
=
3
T
(
⌊
n
/
4
⌋
)
+
Θ
(
n
2
)
T(n)=3T(\lfloor n/4 \rfloor )+\Theta(n^2)
T(n)=3T(⌊n/4⌋)+Θ(n2)为例,来寻求该式的一个上界。因为我们知道舍入对求解递归式通常没有影响,因此我们可以为递归式
T
(
n
)
=
3
T
(
⌊
n
/
4
⌋
)
+
c
n
2
T(n)=3T(\lfloor n/4 \rfloor )+cn^2
T(n)=3T(⌊n/4⌋)+cn2创建一棵递归式,其中我们已经将渐进符号改写为隐含的常数系数c>0。
如下图所示,我们构造出递归式。为了方便起见,我们假定n是4的幂,这样子所有的子问题的规模均为正数。
上图为递归式
T
(
n
)
=
3
T
(
⌊
n
/
4
⌋
)
+
c
n
2
T(n)=3T(\lfloor n/4 \rfloor)+cn^2
T(n)=3T(⌊n/4⌋)+cn2的构造递归式,(a)显示了T(n),在(b)~(d)中逐步扩展为递归树的形式。(d)中显示了扩展完毕的递归树,其高度为
l
o
g
4
n
log_4 n
log4n,(有
l
o
g
4
n
+
1
log_4 n+1
log4n+1层)
接下来我们确定树的每一层的代价。每层的结点数都是上一层的3倍,因此深度为i的结点数为
3
i
3^i
3i.因为每一层子问题规模都是上一层的1/4
,所以对i=0,i,2,…,
l
o
g
4
n
−
1
log_4n-1
log4n−1,深度为i的每个结点的代价为
c
(
n
/
4
i
)
2
c(n/4^i)^2
c(n/4i)2 。做一下乘法可得,对i=0,1,2,…,
l
o
g
4
i
−
1
log_4 i-1
log4i−1,深度为i的所有结点的总代价为
3
i
c
(
n
/
4
i
)
2
=
(
3
/
16
)
i
c
n
2
3^ic(n/4^i)^2=(3/16)^icn^2
3ic(n/4i)2=(3/16)icn2,树的最底层深度为
l
o
g
4
n
log_4 n
log4n,,有
3
l
o
g
4
n
=
n
l
o
g
4
3
3^{log_4 n}=n^{log_43}
3log4n=nlog43个结点,每个结点的代价为T(1),总代价为
n
l
o
g
4
3
T
(
1
)
n^{log_43}T(1)
nlog43T(1),即
Θ
(
n
l
o
g
4
3
)
\Theta(n^{log_43})
Θ(nlog43),因此假定T(1)是常量。
现在我们求所有层次的代价之和,确定整棵树的代价:
T
(
n
)
=
c
2
+
(
3
/
16
)
2
c
n
2
+
.
.
.
+
(
3
/
16
)
l
o
g
4
n
−
1
c
n
2
+
Θ
(
n
l
o
g
4
3
)
=
∑
i
=
0
l
o
g
4
n
−
1
(
3
16
)
i
c
n
2
+
Θ
(
n
l
o
g
4
3
)
=
(
3
/
16
)
l
o
g
4
n
−
1
(
3
/
16
)
−
1
c
n
2
+
Θ
(
n
l
o
g
4
3
)
T(n)=c^2+(3/16)^2cn^2+...+(3/16)^{log4 n-1}cn^2+\Theta(n^{log_43})\\ =\sum_{i=0}^{log_4n-1}(\frac{3}{16})^icn^2+\Theta(n^{log_43})\\= \frac{(3/16)^{log_4 n}-1}{(3/16)-1}cn^2+\Theta(n^{log_4 3})
T(n)=c2+(3/16)2cn2+...+(3/16)log4n−1cn2+Θ(nlog43)=i=0∑log4n−1(163)icn2+Θ(nlog43)=(3/16)−1(3/16)log4n−1cn2+Θ(nlog43)
最后的这个公式可能不太容易看出来,我们可以利用一定程度的不精确,并且利用无限递减几何级数来作为上界:
T
(
n
)
=
∑
i
=
0
l
o
g
4
n
−
1
(
3
16
)
i
c
n
2
+
Θ
(
n
l
o
g
4
3
)
<
∑
i
=
0
∞
(
3
16
)
i
c
n
2
+
Θ
(
n
l
o
g
4
3
)
=
1
1
−
(
3
/
16
)
c
n
2
+
Θ
(
n
l
o
g
4
3
)
=
16
/
13
c
n
2
+
Θ
(
n
l
o
g
4
3
)
=
O
(
n
2
)
T(n)= \sum_{i=0}^{log_4n-1}(\frac{3}{16})^icn^2+\Theta(n^{log_43})<\sum_{i=0}^{\infty}(\frac{3}{16})^icn^2+\Theta(n^{log_43})\\ =\frac{1}{1-(3/16)}cn^2+\Theta(n^{log_43})\\ =16/13cn^2+\Theta(n^{log_43})=O(n^2)
T(n)=i=0∑log4n−1(163)icn2+Θ(nlog43)<i=0∑∞(163)icn2+Θ(nlog43)=1−(3/16)1cn2+Θ(nlog43)=16/13cn2+Θ(nlog43)=O(n2)
这样,对于原始的递归式,我们就推导出了一个猜测
T
(
n
2
)
=
O
(
n
2
)
T(n^2)=O(n^2)
T(n2)=O(n2)。实际上,如果
O
(
n
2
)
O(n^2)
O(n2)确实是递归式的上界,那么它必然是一个紧确界。因为第一次递归调用的代价是
Θ
(
n
2
)
\Theta(n^2)
Θ(n2),因此
Ω
(
n
2
)
\Omega(n^2)
Ω(n2)必然还是递归式的一个下界。
现在,我们使用代入的方法去验证猜测是正确的,即
T
(
n
)
=
O
(
n
2
)
T(n)=O(n^2)
T(n)=O(n2)是递归式的一个上界。我们希望证明
T
(
n
)
≤
d
n
2
T(n)\leq dn^2
T(n)≤dn2对某个常数d>0成立,令常数c>0,我们有:
T
(
n
)
≤
3
T
(
⌊
n
/
4
⌋
)
+
c
(
n
2
)
≤
3
d
⌊
n
/
4
⌋
2
+
c
n
2
≤
3
d
(
n
/
4
)
2
+
c
n
2
=
3
16
d
n
2
≤
d
n
2
T(n)\leq 3T(\lfloor n/4 \rfloor )+c(n^2) \leq 3d \lfloor n/4 \rfloor ^2+cn^2\leq3d(n/4)^2+cn^2=\frac{3}{16}dn^2\\ \leq dn^2
T(n)≤3T(⌊n/4⌋)+c(n2)≤3d⌊n/4⌋2+cn2≤3d(n/4)2+cn2=163dn2≤dn2
当
d
≥
(
16
/
13
)
c
d\geq(16/13)c
d≥(16/13)c时,最后一步推导成立。
三、第二个例子
相信通过第一道题目,读者已经对于生成树有了基本的了解了,我们再来看一道题目。
T
(
n
)
=
T
(
n
/
3
)
+
T
(
2
n
/
3
)
+
O
(
n
)
T(n)=T(n/3)+T(2n/3)+O(n)
T(n)=T(n/3)+T(2n/3)+O(n)
我们令c表示O(n)项中的常数因子。对图中显示出的递归树中的每个层次,当求代价之和的时候,我们发现每层的代价均为cn,从根到叶的最长简单路径是n->(2/3)n->
(
2
/
3
)
2
n
−
>
.
.
.
−
>
1
(2/3)^2n->...->1
(2/3)2n−>...−>1。由于当
k
=
l
o
g
3
/
2
n
k=log_{3/2}n
k=log3/2n时,
(
2
/
3
)
k
n
=
1
(2/3)^kn=1
(2/3)kn=1,因此树高就是
l
o
g
3
/
2
n
log_{3/2}n
log3/2n
在直觉上,我们期望递归式的解最多是层数乘以每层的代价,即
O
(
c
n
l
o
g
3
/
2
n
)
=
O
(
n
l
g
n
)
O(cnlog_{3/2}n)=O(nlgn)
O(cnlog3/2n)=O(nlgn),但上图其实只是展示了递归树的顶部几层,并不是递归树中每个层次的代价都是cn。
考虑叶结点的代价:如果递归树是一棵高度为
l
o
g
3
/
2
n
log_{3/2}n
log3/2n的完全二叉树,则叶子结点的数量为
2
l
o
g
3
/
2
n
=
n
l
o
g
3
/
2
2
2^{log_{3/2}n}=n^{log_{3/2}2}
2log3/2n=nlog3/22,由于每个叶结点的代价为常数,因此所有叶结点的总代价为
Θ
(
n
l
o
g
3
/
2
2
)
\Theta(n^{log_{3/2}2})
Θ(nlog3/22),由于
n
l
o
g
3
/
2
2
n^{log_{3/2}2}
nlog3/22是严格大于1的常数,因此叶结点代价总和为
Ω
(
n
l
g
n
)
\Omega(nlgn)
Ω(nlgn)。但是递归树并不是什么完全二叉树呀,因此叶结点数量是小于
n
l
o
g
3
/
2
2
n^{log_{3/2}2}
nlog3/22的。
而且,当从根节点逐步向下走时,越来越多的内结点是缺失的。因此,递归树中靠下层次对总代价的贡献小于cn。我们可以计算出所有代价的准确值,但是请记住,在递归树中,我们只是希望做一个猜测,用于代入法。我们要忍受一些不精确,尝试证明猜测的上界O(nlgn)是正确的。
即证明
T
(
n
)
≤
d
n
l
g
n
T(n)\leq dnlgn
T(n)≤dnlgn,其中d是一个适当的正常数,我们有:
T
(
n
)
≤
T
(
n
/
3
)
+
T
(
2
n
/
3
)
+
c
n
≤
d
(
n
/
3
)
l
g
(
n
/
3
)
+
d
(
2
n
/
3
)
l
g
(
2
n
/
3
)
+
c
n
=
(
d
(
n
/
3
)
l
g
n
−
d
(
n
/
3
)
l
g
3
)
+
(
d
(
2
n
/
3
)
l
g
n
−
d
(
2
n
/
3
)
l
g
(
3
/
2
)
)
+
c
n
=
d
n
l
g
n
−
d
(
(
n
/
3
)
l
g
3
+
(
2
n
/
3
)
l
g
(
3
/
2
)
)
+
c
n
=
d
n
l
g
n
−
d
(
(
n
/
3
)
l
g
3
+
(
2
n
/
3
)
l
g
3
−
(
2
n
/
3
)
l
g
2
)
+
c
n
=
d
n
l
g
n
−
d
n
(
l
g
3
−
2
/
3
)
+
c
n
≤
d
n
l
g
n
T(n)\leq T(n/3)+T(2n/3)+cn \\ \leq d(n/3)lg(n/3)+d(2n/3)lg(2n/3)+cn\\ =(d(n/3)lgn-d(n/3)lg3)+(d(2n/3)lgn-d(2n/3)lg(3/2))+cn\\ =dnlgn-d((n/3)lg3+(2n/3)lg(3/2))+cn\\ =dnlgn-d((n/3)lg3+(2n/3)lg3-(2n/3)lg2)+cn\\ =dnlgn-dn(lg3-2/3)+cn\\ \leq dnlgn
T(n)≤T(n/3)+T(2n/3)+cn≤d(n/3)lg(n/3)+d(2n/3)lg(2n/3)+cn=(d(n/3)lgn−d(n/3)lg3)+(d(2n/3)lgn−d(2n/3)lg(3/2))+cn=dnlgn−d((n/3)lg3+(2n/3)lg(3/2))+cn=dnlgn−d((n/3)lg3+(2n/3)lg3−(2n/3)lg2)+cn=dnlgn−dn(lg3−2/3)+cn≤dnlgn
只要
d
≥
c
/
(
l
g
3
−
(
2
/
3
)
)
d\ge c/(lg3-(2/3))
d≥c/(lg3−(2/3))。因此,无需对递归树的代价进行更加精确的计算
总结
递归树法是求递归式的一个很实用的方法,我在此做一个笔记,也希望可以帮助到你!
参考文档: 《算法导论》