分治策略
1 引言
《算法导论》第四章的标题是分治策略,这章先讲了最大子数组问题和矩阵乘法的Strassen算法,然后讲了求解递归式的方法,带入法、递归树法和主定理法。不过在之前也提到过使用分治策略的归并排序,之后的快速排序也是采用的分治策略,因为快速排序涉及的内容比较复杂,后续博客会单独介绍。
2 分治策略
2.1 分治策略的递归式
分治策略是将问题分解为小问题,然后将小问题解决了,合并小问题的解,得到问题的解。
由上面简单的描述可以看出分治策略主要的三个步骤——分解、解决、合并。
若问题规模被划分的足够小,即对于某个常量
c
,
n
≤
c
有
T
(
n
)
=
Θ
(
1
)
c, n \leq c 有T(n)=\Theta(1)
c,n≤c有T(n)=Θ(1)。假设把原问题分解为
a
a
a个子问题,每个子问题的规模是原问题的
1
b
\frac1b
b1。那么,为了求解一个
n
b
\frac nb
bn的子问题,需要
a
T
(
n
b
)
aT(\frac nb)
aT(bn)来求解
a
a
a个子问题。如果分解问题需要时间
D
(
n
)
D(n)
D(n),合并问题的解为原问题的解需要时间
C
(
n
)
C(n)
C(n),那么递归式为:
T
(
n
)
=
{
Θ
(
1
)
若
n
≤
c
a
T
(
n
b
)
+
D
(
n
)
+
C
(
n
)
其
他
T(n)=\left\{ \begin{aligned} \Theta(1)&&&&若n\leq c\\ aT(\frac nb)+D(n)+C(n) &&&&其他 \end{aligned} \right.
T(n)=⎩⎨⎧Θ(1)aT(bn)+D(n)+C(n)若n≤c其他
若令
f
(
n
)
=
D
(
n
)
+
C
(
n
)
f(n)=D(n)+C(n)
f(n)=D(n)+C(n),那么递归式为:
T
(
n
)
=
{
Θ
(
1
)
若
n
≤
c
a
T
(
n
b
)
+
f
(
n
)
其
他
T(n)=\left\{ \begin{aligned} \Theta(1)&&&&若n\leq c\\ aT(\frac nb)+f(n) &&&&其他 \end{aligned} \right.
T(n)=⎩⎨⎧Θ(1)aT(bn)+f(n)若n≤c其他
2.2 归并排序
分解: 归并排序的分解是数组下标运算,是常量时间。
合并: 在考虑最坏情况下,例如合并
[
1
,
3
,
5
,
7
,
9
]
[
2
,
4
,
6
,
8
,
10
]
[1,3,5,7,9][2,4,6,8,10]
[1,3,5,7,9][2,4,6,8,10](参考伪代码),归并排序的合并时间跟合并序列长度有关,为序列长度的二倍。
合并操作伪代码:
func merge(A, p, q, r):
n1=q-1+1
n2=r-q
let L[1...n1+1] and R[1...n2+1] be new arrays
for i = 1 to n1
L[i]=A[p+i-1]
for j = 1 to n2
R[j]=A[q+j]
max = 9999
L[n1+1]=max
L[n2+1]=max
i=1
j=1
for k=p to r
if L[i]<R[j]
A[k]=L
i++
if L[i]>R[j]
A[k]=R
j++
return
附: 在函数merge(A,p,q,r)中,A为排序的数组,函数的功能是合并已经排序好的子数组A[p:q]和A[q+1:r]。
解决: 归并排序划分为最小问题的时候是单个元素,对单个元素只有一次访问,因此也是常数时间。
归并排序伪代码:
MERGE-SORT(A,p,r)
if p<r
q = (p+r)/2
MERGE-SORT(A,p,q)
MERGE-SORT(A,q+1,r)
MERGE(A,p,q,r)
归并排序的递归式:
由之前的分析可以知道,归并排序的递归式为:
T
(
n
)
=
{
Θ
(
1
)
n
=
1
2
T
(
n
2
)
+
n
n
>
1
T(n)=\left\{ \begin{aligned} \Theta(1)&&&&n= 1\\ 2T(\frac n2)+n &&&&n>1 \end{aligned} \right.
T(n)=⎩⎨⎧Θ(1)2T(2n)+nn=1n>1
归并排序的运行时间:
Θ
(
n
l
o
g
n
)
\Theta(nlogn)
Θ(nlogn)
2.3 查找最小子数组
求数组 A = [ 13 , − 3 , − 25 , 20 , − 3 , − 16 , − 23 , 18 , 20 , − 7 , 12 , − 5 , − 22 , 15 , − 4 , 7 ] A=[13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7] A=[13,−3,−25,20,−3,−16,−23,18,20,−7,12,−5,−22,15,−4,7]中的最大子数组,即求 i , j i,j i,j使得 m a x A [ i : j ] maxA[i:j] maxA[i:j]。书中给了两种解法,并分析了其运行时间。一种是暴力破解,另外一种是根据分治策略设计的算法。
2.3.1 暴力求解
暴力求解是穷举所有可能的子数组,然后比较得到其最大的数组值。因此总共有 ( n 2 ) {n\choose 2} (2n)种子数组组合,之后还要比较求出其中最大值。因此,暴力求解的下界为 Ω ( n 2 ) \Omega(n^2) Ω(n2)。
2.3.2 分治策略求解最大子数组问题
假如要寻找子数组 A [ l o w : h i g h ] A[low:high] A[low:high]的最大子数组,使用分治策略意味尽量将数组分为等长的两个子数组(分治策略一般将问题分为等长情况,也有不等长的情况,后面求解分治策略的Akra-Bazzi方法就是解决这一类问题的)。若数组的中央为 m i d mid mid,那么子数组为 A [ l o w : m i d ] A[low:mid] A[low:mid]和 A [ m i d + 1 : h i g h ] A[mid+1:high] A[mid+1:high],那么任意一连续子数组 A [ i : j ] A[i:j] A[i:j]必然属于下列三种情况之一:
- 完全位于子数组 A [ l o w : m i d ] A[low:mid] A[low:mid]中,因此有 l o w ≤ i ≤ j ≤ m i d low \leq i \leq j \leq mid low≤i≤j≤mid
- 完全位于子数组 A [ m i d + 1 : h i g h ] A[mid+1:high] A[mid+1:high]中,因此有 m i d + 1 ≤ i ≤ j ≤ h i g h mid+1 \leq i \leq j \leq high mid+1≤i≤j≤high
- 跨越了中点,因此 l o w ≤ i ≤ m i d < j ≤ h i g h low \leq i \leq mid < j \leq high low≤i≤mid<j≤high
那么最小子数组必然处于上述三种情况中的一种。
先考虑第三种情况,最大子数组跨越了中点的情况。因为子数组必定跨越重点
m
i
d
mid
mid,那么分别扫描计算
l
o
w
low
low到
m
i
d
mid
mid和
m
i
d
+
1
mid+1
mid+1到
h
i
g
h
high
high就可以得到跨越中点的最大子数组。因为遍历了两个子数组,因此所用时间相当于两个子数组长度之和。伪代码如下:
FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)
// 扫描左子数组
left-sum=-9999
sum = 0
for i = mid downto low
sum = sum + A[i]
if sum>left-sum
left-sum = sum
max-left = i
// 扫描右子数组
right-sum=-9999
sum = 0
for j = mid downto low
sum = sum + A[j]
if sum>right-sum
right-sum = sum
max-right= j
return (max-left, max-right, left-sum+right-sum)
该伪代码的输入是一个数组,以及其子数组范围及中点坐标。输出是最大子数组范围以及最大子数组中元素之和。
有了解决最大子数组跨越中点的函数,现在就可以采用分治策略求解最大子数组问题了,伪代码如下:
FIND-MAXIMUM-SUBARRAY(A, low, high)
if high == low
return (low, high, A[low]) //数组只有一个元素的情况
else mid=(low+high)/2 //求数组的中点并赋值
(left-low, left-high, left-sum) = FIND-MAXIMUM-SUBARRAY(A, low, mid) //求左子数组的最大子数组
(right-low, right-high, right-sum) = FIND-MAXIMUM-SUBARRAY(A, low, mid) //求右子数组的最大子数组
(cross-low, cross-high, cross-sum) = FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high) //求跨越中点的最大子数组
if left-sum >= right-sum and left-sum >= cross-sum //当最大子数组在左子数组中
return (left-low, left-high, left-sum)
elseif right-sum >= left-sum and right-sum >= cross-sum //当最大子数组在右子数组中
return (right-low, right-high, right-sum)
else
return (cross-low, cross-high, cross-sum) //当最大子数组跨越中点
分治策略求解最大子数组递归式:
分解:最小子数组问题分解过程是数组下标运算(将数组分为两个子数组),时间是常数时间。
合并: 将子问题合并的过程也是常数时间。
解决: 解决过程左右子数组为递归调用,跨越中点的算法为子数组长度的两倍。
分析FIND-MAXIMUM-SUBARRAY(A, low, high)伪代码,对于
n
=
1
n=1
n=1的情况下,即数组只有一个元素的情况下,一行代码就完成了,即时间花费为
T
(
1
)
=
Θ
(
1
)
T(1)=\Theta(1)
T(1)=Θ(1)。
当
n
>
1
n>1
n>1的情况下,只看非常数时间的几行代码,若总时间为
T
(
n
)
T(n)
T(n),求最大子数组在左右子数组的代码花费时间
2
T
(
n
2
)
2T(\frac n2)
2T(2n),求跨越中点的最大子数组花费的时间为
Θ
(
n
)
\Theta(n)
Θ(n)。那么有:
T
(
n
)
=
2
T
(
n
2
)
+
Θ
(
n
)
T(n)=2T(\frac n2)+\Theta(n)
T(n)=2T(2n)+Θ(n)
综合上述分析得到递归式为:
T
(
n
)
=
{
Θ
(
1
)
n
=
1
2
T
(
n
2
)
+
n
n
>
1
T(n)=\left\{ \begin{aligned} \Theta(1)&&&&n= 1\\ 2T(\frac n2)+n &&&&n>1 \end{aligned} \right.
T(n)=⎩⎨⎧Θ(1)2T(2n)+nn=1n>1
运行时间为:
Θ
(
n
l
o
g
n
)
\Theta(nlogn)
Θ(nlogn)
2.4 矩阵乘法Stressen算法
2.4.1 直接计算
考虑问题两个
n
∗
n
n*n
n∗n的方阵
A
,
B
A,B
A,B相乘。
C
=
A
B
C=AB
C=AB,
C
C
C中的任意元素
c
i
j
=
∑
k
=
1
n
a
i
k
∗
b
k
j
c_{ij}=\sum_{k=1}^{n}{a_{ik}*b_{kj}}
cij=∑k=1naik∗bkj。直接计算的伪代码如下:
SQUARE-MATRIX-MULTIPLY
n=A.rows
let C be a new n*n matrix
for i = 1 to n
for j = 1 to n
cij = 0
for k=1 to n
cij = cij + aik*bkj
很明显,直接计算的代码所用的时间为 Θ ( n 3 ) \Theta(n^3) Θ(n3)
2.4.2 简单分治策略计算
矩阵
A
,
B
,
C
A,B,C
A,B,C均为
n
∗
n
n*n
n∗n的方阵,且
C
=
A
B
C=AB
C=AB,假设
n
n
n为2的幂,那么
n
∗
n
n*n
n∗n的矩阵可以被划分为四个
n
2
∗
n
2
\frac n2 *\frac n2
2n∗2n的子矩阵,因为
n
n
n为2的幂,则只要
n
≥
2
n \geq 2
n≥2即可以保证能分解为子矩阵。
假定将矩阵
A
,
B
,
C
A,B,C
A,B,C均分解为四个
n
2
∗
n
2
\frac n2 *\frac n2
2n∗2n的子矩阵:
A
=
[
A
11
A
12
A
21
A
22
]
B
=
[
B
11
B
12
B
21
B
22
]
C
=
[
C
11
C
12
C
21
C
22
]
A=\left[ \begin{matrix} A_{11} & A_{12} \\ A_{21} & A_{22} \end{matrix} \right] B=\left[ \begin{matrix} B_{11} & B_{12} \\ B_{21} & B_{22} \end{matrix} \right] C=\left[ \begin{matrix} C_{11} & C_{12} \\ C_{21} & C_{22} \end{matrix} \right]
A=[A11A21A12A22]B=[B11B21B12B22]C=[C11C21C12C22]
因此
C
=
A
B
C=AB
C=AB可以表示为:
[
C
11
C
12
C
21
C
22
]
=
[
A
11
A
12
A
21
A
22
]
∗
[
B
11
B
12
B
21
B
22
]
\left[ \begin{matrix} C_{11} & C_{12} \\ C_{21} & C_{22} \end{matrix} \right] =\left[ \begin{matrix} A_{11} & A_{12} \\ A_{21} & A_{22} \end{matrix} \right] *\left[ \begin{matrix} B_{11} & B_{12} \\ B_{21} & B_{22} \end{matrix} \right]
[C11C21C12C22]=[A11A21A12A22]∗[B11B21B12B22]
等价于:
C
11
=
A
11
∗
B
11
+
A
12
∗
B
21
C
12
=
A
11
∗
B
12
+
A
12
∗
B
22
C
21
=
A
21
∗
B
11
+
A
22
∗
B
21
C
22
=
A
21
∗
B
12
+
A
22
∗
B
22
C_{11}= A_{11}*B_{11}+A_{12} *B_{21} \\ C_{12}= A_{11}*B_{12}+A_{12} *B_{22} \\ C_{21}= A_{21}*B_{11}+A_{22} *B_{21} \\ C_{22}= A_{21}*B_{12}+A_{22} *B_{22}
C11=A11∗B11+A12∗B21C12=A11∗B12+A12∗B22C21=A21∗B11+A22∗B21C22=A21∗B12+A22∗B22
上述每个公式对应一个
n
2
∗
n
2
\frac n2 *\frac n2
2n∗2n的加法和两个
n
2
∗
n
2
\frac n2 *\frac n2
2n∗2n的乘法。
有了这些只是的铺垫,递归分治算法为:
SQUARE-MATRIX-MULTIPLY-RECURSIVE(A,B)
n = A.rows
let C be a new n*n matrix
if n==1
c11=a11*b11 //矩阵只有一个元素的时候
else 分解 A,B,C三个矩阵
C11 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A11,B11) +SQUARE-MATRIX-MULTIPLY-RECURSIVE(A12,B21)
C11 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A11,B12) +SQUARE-MATRIX-MULTIPLY-RECURSIVE(A12,B22)
C11 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A21,B11) +SQUARE-MATRIX-MULTIPLY-RECURSIVE(A22,B21)
C11 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A21,B12) +SQUARE-MATRIX-MULTIPLY-RECURSIVE(A22,B22)
当
n
=
1
n=1
n=1时,只计算一个元素,即一次标量乘法,因此有:
T
(
1
)
=
Θ
(
1
)
T(1)=\Theta(1)
T(1)=Θ(1)
当
n
>
1
n>1
n>1的时候是递归情况,在一次递归调用总共计算了8次
n
2
∗
n
2
\frac n2 *\frac n2
2n∗2n的乘法,花费时间为
8
T
(
n
2
)
8T(\frac n2)
8T(2n),除此之外,还计算了四次
n
2
∗
n
2
\frac n2 *\frac n2
2n∗2n的加法,由于每个矩阵有
n
2
4
\frac{n^2}{4}
4n2,因此花费时间为
Θ
(
n
2
)
\Theta(n^2)
Θ(n2)。因此递归式可以表达为:
T
(
n
)
=
{
Θ
(
1
)
n
=
1
8
T
(
n
2
)
+
Θ
(
n
2
)
n
>
1
T(n)=\left\{ \begin{aligned} \Theta(1)&&&&n= 1\\ 8T(\frac n2)+\Theta(n^2) &&&&n>1 \end{aligned} \right.
T(n)=⎩⎨⎧Θ(1)8T(2n)+Θ(n2)n=1n>1
根据主定理计算此递归式的运行时间为
T
(
n
)
=
Θ
(
n
3
)
T(n)=\Theta(n^3)
T(n)=Θ(n3)。
2.4.3 Strassen 方法求矩阵乘法
在2.4.2节的简单的分治策略中分成了八个长度为原矩阵一半的矩阵乘法和四次矩阵加法。而Strassen方法是利用数学技巧,减少了一次乘法运算,增加了矩阵的加法运算。Strassen算法步骤具体如下:
- 按2.4.2中的方法,将输入矩阵 A , B A,B A,B和输出矩阵 C C C分别分解为四个 n 2 ∗ n 2 \frac n2 *\frac n2 2n∗2n的子矩阵(分解矩阵若采用下标运算花费时间为 Θ ( 1 ) \Theta(1) Θ(1)).
- 创建10个 n 2 ∗ n 2 \frac n2 *\frac n2 2n∗2n的矩阵 S 1 , S 2 , S 3 , S 4 , S 5 , S 6 , S 7 , S 8 , S 9 , S 10 S_1,S_2,S_3,S_4,S_5,S_6,S_7,S_8,S_9,S_{10} S1,S2,S3,S4,S5,S6,S7,S8,S9,S10,每个矩阵保存步骤1中矩阵 A , B A,B A,B的子矩阵的和或差。
- 利用步骤1和步骤2的矩阵,递归的计算7个矩阵之积, P 1 , P 2 , P 3 , P 4 , P 5 , P 6 , P 7 P_1,P_2,P_3,P_4,P_5,P_6,P_7 P1,P2,P3,P4,P5,P6,P7.
- 通过组合不同的 P i P_i Pi矩阵计算结果子矩阵 C 11 , C 12 , C 21 , C 22 C_{11},C_{12},C_{21},C_{22} C11,C12,C21,C22.
一开始说了strassen方法是相比于2.4.2的算法少了一次乘法,多了几次
n
2
∗
n
2
\frac n2 *\frac n2
2n∗2n的加法,具体多了几次对算法的效率影响不大,若有兴趣可以根据后续的具体步骤去计算,即有限次加法均可以表示为
Θ
(
n
2
)
\Theta(n^2)
Θ(n2),得到Strassen算法的的递归式为:
T
(
n
)
=
{
Θ
(
1
)
n
=
1
7
T
(
n
2
)
+
Θ
(
n
2
)
n
>
1
T(n)=\left\{ \begin{aligned} \Theta(1)&&&&n= 1\\ 7T(\frac n2)+\Theta(n^2) &&&&n>1 \end{aligned} \right.
T(n)=⎩⎨⎧Θ(1)7T(2n)+Θ(n2)n=1n>1
根据主定理可以算出递归式的解为:
T
(
n
)
=
Θ
(
n
l
g
7
)
T(n)=\Theta(n^{lg7})
T(n)=Θ(nlg7)
现在说明每一步矩阵的创建过程。
第二步中创建的10个矩阵为:
S
1
=
B
12
−
B
22
S
2
=
A
11
+
A
12
S
3
=
A
21
+
A
22
S
4
=
B
21
−
B
11
S
5
=
A
11
+
A
22
S
6
=
B
11
+
B
22
S
7
=
A
12
−
A
22
S
8
=
B
21
+
B
22
S
9
=
A
11
−
A
21
S
10
=
B
11
+
B
12
S_1=B_{12}-B_{22} \\ S_2=A_{11}+A_{12} \\ S_3=A_{21}+A_{22} \\ S_4=B_{21}-B_{11} \\ S_5=A_{11}+A_{22} \\ S_6=B_{11}+B_{22} \\ S_7=A_{12}-A_{22} \\ S_8=B_{21}+B_{22} \\ S_9=A_{11}-A_{21} \\ S_{10}=B_{11}+B_{12}
S1=B12−B22S2=A11+A12S3=A21+A22S4=B21−B11S5=A11+A22S6=B11+B22S7=A12−A22S8=B21+B22S9=A11−A21S10=B11+B12
第三步中递归计算的7个
n
2
∗
n
2
\frac n2 *\frac n2
2n∗2n矩阵的乘法,如下所示:
P
1
=
A
11
∗
S
1
=
A
11
∗
B
12
−
A
11
∗
B
22
P
2
=
S
2
∗
B
22
=
A
11
∗
B
22
+
A
12
∗
B
22
P
3
=
S
3
∗
B
11
=
A
21
∗
B
11
+
A
22
∗
B
11
P
4
=
A
22
∗
S
4
=
A
22
∗
B
21
−
A
22
∗
B
11
P
5
=
S
5
∗
S
6
=
A
11
∗
B
11
+
A
11
∗
B
22
+
A
22
∗
B
11
+
A
22
∗
B
22
P
6
=
S
7
∗
S
8
=
A
12
∗
B
21
+
A
12
∗
B
22
−
A
22
∗
B
21
−
A
22
∗
B
22
P
7
=
S
9
∗
S
10
=
A
11
∗
B
11
+
A
11
∗
B
12
−
A
21
∗
B
11
−
A
21
∗
B
12
P_1=A_{11}*S_1=A_{11}*B_{12}-A_{11}*B_{22} \\ P_2=S_2*B_{22}=A_{11}*B_{22}+A_{12}*B_{22} \\ P_3=S_3*B_{11}=A_{21}*B_{11}+A_{22}*B_{11} \\ P_4=A_{22}*S_4=A_{22}*B_{21}-A_{22}*B_{11} \\ P_5=S_5*S_6=A_{11}*B_{11}+A_{11}*B_{22}+A_{22}*B_{11}+A_{22}*B_{22} \\ P_6=S_7*S_8=A_{12}*B_{21}+A_{12}*B_{22}-A_{22}*B_{21}-A_{22}*B_{22} \\ P_7=S_9*S_{10}=A_{11}*B_{11}+A_{11}*B_{12}-A_{21}*B_{11}-A_{21}*B_{12} \\
P1=A11∗S1=A11∗B12−A11∗B22P2=S2∗B22=A11∗B22+A12∗B22P3=S3∗B11=A21∗B11+A22∗B11P4=A22∗S4=A22∗B21−A22∗B11P5=S5∗S6=A11∗B11+A11∗B22+A22∗B11+A22∗B22P6=S7∗S8=A12∗B21+A12∗B22−A22∗B21−A22∗B22P7=S9∗S10=A11∗B11+A11∗B12−A21∗B11−A21∗B12
第四步,通过组合
P
i
P_i
Pi矩阵得到
C
C
C的四个
n
2
∗
n
2
\frac n2 *\frac n2
2n∗2n子矩阵:
C
11
=
P
5
+
P
4
−
P
2
+
P
6
C
12
=
P
1
+
P
2
C
21
=
P
3
+
P
4
C
22
=
P
5
+
P
1
−
P
3
−
P
7
C_{11}=P_5+P_4-P_2+P_6 \\ C_{12}=P_1+P_2 \\ C_{21}=P_3+P_4 \\ C_{22}=P_5+P_1-P_3-P_7
C11=P5+P4−P2+P6C12=P1+P2C21=P3+P4C22=P5+P1−P3−P7
总结:在步骤四中共执行了8次
n
2
∗
n
2
\frac n2 *\frac n2
2n∗2n加减法,在步骤三中执行了10次
n
2
∗
n
2
\frac n2 *\frac n2
2n∗2n加减法,因此Strassen方法共执行7次
n
2
∗
n
2
\frac n2 *\frac n2
2n∗2n乘法,18次
n
2
∗
n
2
\frac n2 *\frac n2
2n∗2n加法(不过其时间仍然是
Θ
(
n
2
)
\Theta(n^2)
Θ(n2)),执行时间为
Θ
(
n
l
g
7
)
\Theta(n^{lg7})
Θ(nlg7);简单的分治策略执行了8次
n
2
∗
n
2
\frac n2 *\frac n2
2n∗2n乘法,4次
n
2
∗
n
2
\frac n2 *\frac n2
2n∗2n加法,执行时间为
Θ
(
n
3
)
\Theta(n^3)
Θ(n3)。
2.5 分治策略递归式的求解
2.5.1 代入法
代入法步骤有两步:
- 猜测解的形式;
- 用数学归纳法证明其形式;
例如,归并排序的递归式为:
T ( n ) = { Θ ( 1 ) n = 1 2 T ( n 2 ) + n n > 1 T(n)=\left\{ \begin{aligned} \Theta(1)&&&&n= 1\\ 2T(\frac n2)+n &&&&n>1 \end{aligned} \right. T(n)=⎩⎨⎧Θ(1)2T(2n)+nn=1n>1
猜想此递归式的上界为 O ( n l g n ) O(nlgn) O(nlgn),假设问题的规模为2的幂,则
- 当n=4时,有
T ( 4 ) = 2 T ( 2 ) + 8 = 4 c + 12 , O ( n l g n ) = c 4 l g 4 = 8 c T(4)=2T(2)+8=4c+12,O(nlgn)=c4lg4=8c T(4)=2T(2)+8=4c+12,O(nlgn)=c4lg4=8c,因此当 c ≥ 3 c\geq 3 c≥3使得边界条件成立 - 假设当 n = k n=k n=k时成立则有式子: Θ ( T ( k ) ) ≤ Θ ( k l g k ) 成 立 , 即 : T ( k ) ≤ c k l g k 成 立 \Theta(T(k)) \leq \Theta(klgk)成立,即:T(k) \leq cklgk成立 Θ(T(k))≤Θ(klgk)成立,即:T(k)≤cklgk成立
- 当
n
=
2
k
n=2k
n=2k时
T ( 2 k ) = 2 T ( k ) + 4 k ≤ 2 c k l g k + 4 k T(2k)=2T(k)+4k \leq 2cklgk + 4k T(2k)=2T(k)+4k≤2cklgk+4k
而 c ∗ 2 k l g 2 k = 2 c k + 2 c k l g 2 k c*2klg2k=2ck+2cklg2k c∗2klg2k=2ck+2cklg2k
因此, ∃ c ≥ 2 \exist c\geq 2 ∃c≥2使得不等式成立。
由上可知,只要存在一个足够大的
c
c
c值,均可以使得不等式
Θ
(
T
(
n
)
)
≤
Θ
(
n
l
g
n
)
\Theta(T(n)) \leq \Theta(nlgn)
Θ(T(n))≤Θ(nlgn)成立。
代入法在第一猜测中非常依赖经验,在证明过程中也会容易踩坑。
2.5.2 递归树法
递归树是一种很形象的求解递归式的方法,以递归式
T
(
n
)
=
3
T
(
⌊
n
/
4
⌋
)
+
Θ
(
n
2
)
T(n)=3T(\lfloor n/4\rfloor)+\Theta(n^2)
T(n)=3T(⌊n/4⌋)+Θ(n2) 为例子。递归树如图所示:
递归树的构建过程如上图所示,计算每一层之和就可以得到总的运算时间;
首先看树高,算法每一次递归分成3个子问题,而每个子问题是原问题的
1
4
\frac14
41,因此树的高度为
l
o
g
4
n
log_4n
log4n。比如:当n为16时,第一次递归分为3个规模为4的子问题,第二次计算前面的子问题即问题规模为4的问题,此时分为3个规模为1的子问题。因此树高与递归子问题的规模有对数关系
l
o
g
4
n
log_4n
log4n。
第一层:第一层由于分解成了三个子问题,问题本身分解和合并的代价(在最开始分治策略的递归式说明了除了子问题的描述之外,其余的代价就是子问题的合并与分解所产生的)为
Θ
(
n
2
)
\Theta(n^2)
Θ(n2),即
c
n
2
cn^2
cn2;
第二层:第二层有三个子问题被分解了,每个子问题本身的分解和合并的代价为
1
16
c
n
2
\frac{1}{16}cn^2
161cn2,总共有三个子问题,因此总代价为
3
16
c
n
2
\frac{3}{16}cn^2
163cn2;
第三层:每一层都是被分成三个规模为原问题规模
1
4
\frac14
41的问题,因此,每一层的代价为之前层代价的
3
4
2
\frac{3}{4^2}
423;第三层的代价和为
(
3
16
)
2
c
n
2
(\frac{3}{16})^2cn^2
(163)2cn2;
倒数第二层:这一层就已经将问题划分为最小的单位了,但是这一层求解的代价依然为问题的合成与分解的代价,因此需要最后一层求解最小子问题的代价和,这一层代价和为
(
3
16
)
l
o
g
4
n
−
1
c
n
2
(\frac{3}{16})^{log_4n-1}cn^2
(163)log4n−1cn2
最后一层:最后一层具有特殊性,因为是划分的最小单位,每个代价都为
Θ
(
1
)
\Theta(1)
Θ(1)即任意常数,因此代价和为
个
数
∗
Θ
(
1
)
个数*\Theta(1)
个数∗Θ(1);现在问题就在于求解这个"个数"。
计算个数的过程为:假设
n
n
n为4的幂,即:
n
=
4
k
n=4^k
n=4k则:
T
(
4
k
)
=
3
T
(
4
k
−
1
)
+
Θ
(
n
2
)
=
3
2
T
(
4
k
−
2
)
+
.
.
.
.
.
.
=
3
k
T
(
1
)
.
.
.
\begin{aligned}T(4^k) &=3T(4^{k-1})+\Theta(n^2) \\ &=3^2T(4^{k-2})+... \\ &...\\ &=3^kT(1)... \end{aligned}
T(4k)=3T(4k−1)+Θ(n2)=32T(4k−2)+......=3kT(1)...
3
k
3^k
3k为最后一层问题的个数,其中
k
=
l
o
g
4
n
k=log_4n
k=log4n,因此个数为
3
l
o
g
4
n
=
n
l
o
g
4
3
3^{log_4n}=n^{log_43}
3log4n=nlog43,所以最后一层的代价为
Θ
(
n
l
o
g
4
3
)
\Theta(n^{log_43})
Θ(nlog43)。
综上总代价为:
T
(
n
)
=
c
n
2
+
3
16
c
n
2
+
(
3
16
)
2
c
n
2
.
.
.
+
(
3
16
)
l
o
g
4
n
−
1
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
)
\begin{aligned}T(n) &=cn^2+\frac{3}{16}cn^2+(\frac{3}{16})^2cn^2...+(\frac{3}{16})^{log_4n-1}cn^2+\Theta(n^{log_43}) \\ &=\frac{(\frac{3}{16})^{log_4n}-1}{\frac{3}{16}-1}cn^2+\Theta(n^{log_43}) \\ \end{aligned}
T(n)=cn2+163cn2+(163)2cn2...+(163)log4n−1cn2+Θ(nlog43)=163−1(163)log4n−1cn2+Θ(nlog43)
当
n
→
∞
n\rightarrow \infty
n→∞时,
T
(
n
)
=
−
1
3
16
−
1
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
)
=
Θ
(
n
2
)
\begin{aligned}T(n) &=\frac{-1}{\frac{3}{16}-1}cn^2+\Theta(n^{log_43})\\ &=\frac{1}{1-\frac{3}{16}}cn^2+\Theta(n^{log_43})\\ &=\frac{16}{13}cn^2+\Theta(n^{log_43})\\ &=\Theta(n^2) \end{aligned}
T(n)=163−1−1cn2+Θ(nlog43)=1−1631cn2+Θ(nlog43)=1316cn2+Θ(nlog43)=Θ(n2)
2.5.3 主定理法
相比于前面两种求解递归式的方法,主定理算一种非常方便的方法。主定理如下:
主定理:令
a
≥
1
,
b
>
1
a \geq 1,b>1
a≥1,b>1为常数,
f
(
n
)
f(n)
f(n)为渐进正函数,
T
(
n
)
T(n)
T(n)是定义在非负整数上的递归式:
T
(
n
)
=
a
T
(
n
b
)
+
f
(
n
)
T(n)=aT(\frac nb)+f(n)
T(n)=aT(bn)+f(n)
其中将
n
b
\frac nb
bn解释为
⌊
n
b
⌋
\lfloor\frac nb\rfloor
⌊bn⌋或
⌈
n
b
⌉
\lceil\frac nb\rceil
⌈bn⌉。那么
T
(
n
)
T(n)
T(n)有如下渐进界:
- 若对某个常数 ε > 0 \varepsilon>0 ε>0有 f ( n ) = O ( n l o g b a − ε ) f(n)=O(n^{log_ba-\varepsilon}) f(n)=O(nlogba−ε),则有: T ( n ) = Θ ( n l o g b a ) T(n)=\Theta(n^{log_ba}) T(n)=Θ(nlogba);
- 若 f ( n ) = O ( n l o g b a ) f(n)=O(n^{log_ba}) f(n)=O(nlogba),则 T ( n ) = Θ ( n l o g b a l g n ) T(n)=\Theta(n^{log_ba}lgn) T(n)=Θ(nlogbalgn);
- 若对某个常数 ε > 0 \varepsilon>0 ε>0有 f ( n ) = O ( n l o g b a + ε ) f(n)=O(n^{log_ba+\varepsilon}) f(n)=O(nlogba+ε),且对某个常数 c < 1 c<1 c<1和所有足够大的 n n n有 a f ( n b ) ≤ c f ( n ) af(\frac nb) \leq cf(n) af(bn)≤cf(n),则有: T ( n ) = Θ ( f ( n ) ) T(n)=\Theta(f(n)) T(n)=Θ(f(n));
这里先引入两个概念:
多项式大于: 若
f
(
n
)
f(n)
f(n)多项式大于
g
(
n
)
g(n)
g(n),则:
∃
e
>
0
,
n
0
,
使
得
当
n
>
n
0
,
有
f
(
n
)
>
g
(
n
)
n
e
\exist e>0,n_0,使得当n>n_0,有f(n)>g(n)n^e
∃e>0,n0,使得当n>n0,有f(n)>g(n)ne
多项式小于: 若
f
(
n
)
f(n)
f(n)多项式小于
g
(
n
)
g(n)
g(n),则:
∃
e
>
0
,
n
0
,
使
得
当
n
>
n
0
,
有
f
(
n
)
<
g
(
n
)
n
e
\exist e>0,n_0,使得当n>n_0,有f(n)<g(n)n^e
∃e>0,n0,使得当n>n0,有f(n)<g(n)ne
主定理的第一条的条件的含义是,
f
(
n
)
f(n)
f(n)多项式小于
n
l
o
g
b
a
n^{log_ba}
nlogba,而不是普普通通的小于,例如:
n
<
n
l
g
n
n<nlgn
n<nlgn,但是并不是多项式小于,因为没有满足条件的
ε
>
0
\varepsilon>0
ε>0使得不等式
n
ε
>
l
g
n
n^{\varepsilon}>lgn
nε>lgn;
主定理的第三条条件的含义是多项式大于,而第二个条件的含义是分成的子问题使得,存在足够大的常量
c
,
n
c,n
c,n使得子问题的代价小于子问题合并和分解的代价。
例:求解Strassen方法的递归式,Strassen方法满足主定理第一条,因此有:
T
(
n
)
=
Θ
(
n
l
g
7
)
T(n)=\Theta(n^{lg7})
T(n)=Θ(nlg7)
2.5.4 Akra-Bazzi方法求解递归式
这篇博客从一开始到现在写的内容都是关于均等划分的,当遇到例如: T ( n ) = T ( ⌊ n 3 ⌋ ) + T ( ( ⌊ 2 n 3 ⌋ ) + O ( n ) T(n)=T(\lfloor \frac n3\rfloor)+T((\lfloor \frac{2n}{3}\rfloor)+O(n) T(n)=T(⌊3n⌋)+T((⌊32n⌋)+O(n)这一类非均等划分的分治策略的时候,就应该采用Akra-Bazzi方法,具体方法不做叙述。
3 总结
本篇博客简单的将《算法导论》中分治策略以及设计分治策略的部分进行总结,以供以后参考,若有错误欢迎提出。