《算法导论》第四章的标题是分治策略,这章先讲了最大子数组问题和矩阵乘法的Strassen算法,然后讲了求解递归式的方法,带入法、递归树法和主定理法。不过在之前也提到过使用分治策略的归并排序,之后的快速排序也是采用的分治策略,因为快速排序涉及的内容比较复杂,后续博客会单独介绍。
分治策略是将问题分解为小问题,然后将小问题解决了,合并小问题的解,得到问题的解。
由上面简单的描述可以看出分治策略主要的三个步骤——分解、解决、合并。
若问题规模被划分的足够小,即对于某个常量 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其他
分解: 归并排序的分解是数组下标运算,是常量时间。
合并: 在考虑最坏情况下,例如合并 [ 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]=R
j++
return
附: 在函数merge(A,p,q,r)中,A为排序的数组,函数的功能是合并已经排序好的子数组A[p:q]和A[q+1:r]。
解决: 归并排序划分为最小问题的时候是单个元素,对单个元素只有一次访问,因此也是常数时间。
归并排序伪代码:
MERGE-SORT(A,p,r)
if p
归并排序的递归式:
由之前的分析可以知道,归并排序的递归式为:
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)
求数组 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]。书中给了两种解法,并分析了其运行时间。一种是暴力破解,另外一种是根据分治策略设计的算法。
暴力求解是穷举所有可能的子数组,然后比较得到其最大的数组值。因此总共有 ( n 2 ) {n\choose 2} (2n)种子数组组合,之后还要比较求出其中最大值。因此,暴力求解的下界为 Ω ( n 2 ) \Omega(n^2) Ω(n2)。
假如要寻找子数组 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]必然属于下列三种情况之一:
那么最小子数组必然处于上述三种情况中的一种。
先考虑第三种情况,最大子数组跨越了中点的情况。因为子数组必定跨越重点 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)
考虑问题两个 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)
矩阵 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.2节的简单的分治策略中分成了八个长度为原矩阵一半的矩阵乘法和四次矩阵加法。而Strassen方法是利用数学技巧,减少了一次乘法运算,增加了矩阵的加法运算。Strassen算法步骤具体如下:
一开始说了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)。
代入法步骤有两步:
猜想此递归式的上界为 O ( n l g n ) O(nlgn) O(nlgn),假设问题的规模为2的幂,则
由上可知,只要存在一个足够大的 c c c值,均可以使得不等式 Θ ( T ( n ) ) ≤ Θ ( n l g n ) \Theta(T(n)) \leq \Theta(nlgn) Θ(T(n))≤Θ(nlgn)成立。
代入法在第一猜测中非常依赖经验,在证明过程中也会容易踩坑。
递归树是一种很形象的求解递归式的方法,以递归式 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)
相比于前面两种求解递归式的方法,主定理算一种非常方便的方法。主定理如下:
主定理:令 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)有如下渐进界:
这里先引入两个概念:
多项式大于: 若 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)
主定理的第一条的条件的含义是, f ( n ) f(n) f(n)多项式小于 n l o g b a n^{log_ba} nlogba,而不是普普通通的小于,例如: n < n l g n n
主定理的第三条条件的含义是多项式大于,而第二个条件的含义是分成的子问题使得,存在足够大的常量 c , n c,n c,n使得子问题的代价小于子问题合并和分解的代价。
例:求解Strassen方法的递归式,Strassen方法满足主定理第一条,因此有: T ( n ) = Θ ( n l g 7 ) T(n)=\Theta(n^{lg7}) T(n)=Θ(nlg7)
这篇博客从一开始到现在写的内容都是关于均等划分的,当遇到例如: 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方法,具体方法不做叙述。
本篇博客简单的将《算法导论》中分治策略以及设计分治策略的部分进行总结,以供以后参考,若有错误欢迎提出。