第 4 章 分治策略

在分治策略中,我們遞歸地求解一個問題,在每層遞歸中應用如下三個步驟:
  分解:將問題劃分爲一些子問題,子問題的形式與原問題一樣,只是規模更小。
  解決遞歸地求解出子問題。。如果子問題規模足夠小,則停止遞歸,直接求解。
  合併 將子問題的解組合成原問題的接。
  當子問題足夠大,需要遞歸求解時,我們稱之爲遞歸情況。當子問題變得足夠小,不再需要遞歸時,我們說遞歸已經“觸底”,進入了基本情況
  在本章中,我們將看到更多基於分治策略的算法。第一個算法求解最大子數組問題,其輸入是一個數值數組,算法需要確定具有最大和的連續子數組。然後我們將看到兩個求解n*n的矩陣乘法問題的分治算法。其中一個的運行時間爲Θ(n3 ),並不優於平凡算法。但另一個算法的運行時間爲O(n2.81 ),漸近時間複雜性擊敗了平凡算法。

遞歸式
  一個遞歸式就是一個等式或不等式,它通過更小的輸入上的函數值來描述一個函數。例如,在2.3.2節中,我們用遞歸式描述了MERGE-SORT過程的最壞情況運行時間T(n):
  

T(n)={Θ(1),2T(n/2)+Θ(n),n == 1n >

求解可得T(n)=Θ(nlgn )
  遞歸可以有很多形式。一個遞歸算法可能將問題劃分爲規模不等的子問題,子問題的規模不必是原問題的一個固定比例。例如:線性查找的遞歸版本僅生成一個子問題,其規模僅比原問題的規模少一個元素。每次遞歸調用將花費常量時間在加上下一層遞歸調用的時間,因此遞歸式爲T(n)=T(n1)+Θ(1)
  本章介紹三種求解遞歸式的方法,即得出算法的“Θ”或“O”漸近界的方法。
  
  • 代入法:我們猜測一個接,然後用數學歸納法鄭敏這個界是正確的。
  • 遞歸數法:將遞歸式轉換成一棵樹,其節點表示不同層次的遞歸調用產生的代價,然後採用邊界和技術來求解遞歸式。
  • 主方法:可求解形如下面公式的遞歸式的界:
          T(n)=aT(n/b) + f(n)
       其中a1,b>1 ,f(n)是一個給定的函數。這種形式的遞歸式很常見,它刻畫了這樣一個分治算法:生成a個問題,每個子問題的規模是原問題規模的1/b,分解和合並步驟總共花費時間爲f(n)。
      在本章中,我們將使用主方法來確定最大子數組問題和矩陣相乘問題的分治算法的運行時間。我們偶爾會遇到不是等式而是不等式的遞歸式,例如T(n)2T(n/2)+Θ(n) 因爲這樣一種遞歸式僅描述了T(n)的一個上界,因此可以用O符號而不是Θ符號來描述其解。類似地,如果不等式爲T(n)2T(n/2)+Θ(n) ,則由於遞歸式只給出了T(n)的一個下界,我們應使用Ω符號來描述其解。

遞歸式技術細節
  在實際應用中,我們常常忽略遞歸式的一些技術細節。當聲明、求解遞歸式時,我們常常忽略向下取整、向上取整及邊界條件。但是,在本章中,我們會討論某些細節,展示遞歸式求解方法的要點。

4.1 最大子數組問題

暴力求解
問題變換
  問題轉化爲尋找數組A的和最大的非空連續子數組。稱這樣的連續子數組爲最大子數組
  只有當數組中包含負數時,最大子數組問題纔有意義。
使用分治策略的求解方法
  假定我們要尋找A[low..high]的最大子數組。使用分治技術意味着我們要將子數組劃分爲兩個規模儘量相等的子數組。比如mid,然後考慮求解兩個子數組A[low..mid]和A[mid+1..high]。A[low..high]的任何連續子數組A[i..j]所處的位置必然是以三種情況之一:
  

  • 完全位於子數組A[low..mid]中,因此lowijmid
  • 完全位於子數組A[mid+1..high]中,因此mid<ijhigh
  • 跨越了中點,因此lowimid<jhigh .
    我們可以遞歸地求解A[low..mid]和A[mid+1..high]的最大子數組,因爲這兩個子問題仍是最大子數組問題,只是規模更小。因此剩下的全部工作就是尋找跨越中點的最大子數組,然後在三種情況中選取和最大者。
      我們可以很容易地在線性時間內求出跨越中點的最大子數組。此問題並非原問題規模更小的實例,因爲它加入了限制——求出的子數組必須跨越中點。任何跨越中點的子數組都由兩個子數組A[i..mid]和A[mid+1..j]組成,其中lowimid<jhigh 。因此我們只需找出形如A[i..mid]和A[mid+1..j]的最大子數組,然後將其合併即可。過程FIND-MAX-CROSSING-SUBARRAY接受數組A和下標low、mid、high爲輸入,返回一個下標元組劃定跨越中點的最大子數組的邊界,並返回最大子數組中值的和。
      FIND-MAX-CROSSING-SUBARRAY(A,low,mid,high)  
left-sum = -∞
sum = 0
for i = mid downto low
    sum = sum + A[i]
    if sum > left-sum
        left-sum = sum
        max-left = i
right-sum = -∞
sum = 0
for j = mid + 1 to high
    sum = sum + A[j]
    if sum > right-sum
        right-sum = sum
        max-right = j
return (max-left,max-rght,left-sum+right-sum)

  如果子數組A[low..high]包含n個元素,則調用FIND-MAX-CROSSING-SUBARRAY(A,low,mid,high)花費Θ(n)時間。然後設計求解最大子數組問題的分治算法的僞代碼:
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,mid+1,high)
    (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)
    else if right-sum >= left-sum and right-sum >= cross-sum
        return (right-low, right-high,right-sum)
    else return (cross-low, cross-high,cross-sum)

分治算法的分析
  我們用T(n)表示FIND-MAXIMUM-SUBARRAY求解n個元素的最大子數組的運行時間:
  

T(n)={Θ(1),2T(n/2)+Θ(n),n = 1n>1

此遞歸式與歸併排序的遞歸式一樣。其解爲T(n)=Θ(nlgn )。

4.2 矩陣乘法的Strassen算法

  若A=(aij) 和B=(bij) 是n*n的方陣,定義乘積C=AB
  

cij=k=1naikbkj

  我們需要計算n2 個矩陣元素,每個元素是n個值的和。下面過程接受n*n矩陣A和B,返回它們的乘積——n*n矩陣C。假設每個矩陣都有一個屬性rows,給出矩陣的行數。
  SQUARE-MATRIX-MULTIPLY(A,B)
  
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
return C

由於三重for循環的沒一重都恰好是執行n步。因此過程SQUARE-MATRIX-MULTIPLY花費Θ(n3) 時間。
  在本節中,我們將看到Strassen的著名n*n矩陣相乘的遞歸算法,其運行時間爲Θ(nlg7) .由於lg7 在2.80和2.81之間,因此Strassen算法的運行時間爲O(n2.81) ,漸近複雜性優於簡單的SQUARE-MATRIX-MULTIPLY過程。
一個簡單的分治過程
  爲簡單起見,當使用分治算法計算矩陣積C=AB 時,假定三個矩陣均爲n*n矩陣,其中n爲2的冪。我們做出這個假設是因爲在每個分解步驟中,n*n矩陣都被劃分爲4個n/2*n/2的子矩陣。如果假定n是2冪,則只要n>=2即可保證子矩陣規模爲n/2的整數。
  假定將A、B和C均分解爲4個n/2 * n/2的子矩陣:
  

A=[A11A21A12A22]B=[B11B21B12B22]C=[C11C21C12C22]

因此可以將公式改寫爲:
[C11C21C12C22]=[A11A21A12A22][B11B21B12B22]

等價於下面4個公式:
C11=A11B11+A12B21

C12=A11B12+A12B22

C21=A21B11+A22B21

C22=A21B12+A22B22

可以利用這些公式設計一個直接的遞歸分治算法:
SQUARE-MATRIX-MULTIPLY-RECURSIVE(A,B)
n = A.rows
let C be a new n*n matrix
if n == 1
    c_11 = a_11 · b_11
else partition A,B and C as in equations(4.9)
    C_11 =  SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_11,B_11)
        + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_12,B_21)
    C_12 =  SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_11,B_12)
        + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_12,B_22)
    C_21 =  SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_21,B_11)
        + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_22,B_21)
    C_22 =  SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_21,B_12)
        + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_22,B_22)
return C

這段僞代碼掩蓋了一個微妙但重要的實現細節,第 5 行應該如何分解矩陣?如果我們真的創建12個新的n/2 * n/2矩陣,將會花費Θ(n2 )時間複製矩陣元素。實際上,我們可以通過原矩陣的一組行下標和一組列下標來指明一個子矩陣。這種表示方法的好處是,通過下標計算指明子矩陣,執行第 5 行只需Θ(1)的時間。
  令T(n)表示用此過程計算兩個n*n矩陣乘積的時間。
  

T(n)={Θ(1)  ,8T(n/2)+Θ(n2),n = 1n > 1

利用主方法求解遞歸式,得到的解爲T(n) = Θ(n3) .因此簡單的分治算法並不優於直接的SQUARE-MATRIX-MULTIPLY過程。

Strassen方法
Strassen算法的核心思想是令遞歸數稍微不那麼茂盛第二,即只遞歸進行7次而不是8次n/2 * n/2矩陣的乘法。Strassen算法不是那麼直觀,它包含4個步驟:
1.按公式將輸入矩陣A、B和輸入出矩陣C分解爲n/2 * n/2的子矩陣。採用下標計算方法,此步驟花費Θ(1)時間。
2.創建10個n/2 * n/2的矩陣S1,S2,...,S10 ,每個矩陣保存步驟1中創建的兩個子矩陣的和或差。花費時間Θ(n2 )。
3.用步驟1中創建的子矩陣和b步驟2中創建的10個矩陣,遞歸地計算7個矩陣積P1,P2,...,P7 。每個矩陣都是n/2 * n/2的。
4.通過Pi 矩陣的不同組合進行加減運算,計算出結果矩陣C的子矩陣C11,C12,C21,,C22 ,花費時間Θ(n2 )。
Strassen算法運行時間T(n)的遞歸式:

T(n)={Θ(1)  ,7T(n/2)+Θ(n2),n = 1n > 1

可以求出解爲T(n) = Θ(nlg7)

4.3 用代入法求解遞歸式

代入法求解遞歸是分爲兩步: 
  1.猜測解的形式
  2。用數學歸納法求出解中的常數,並證明解是正確的。
做出好的猜測
猜測解要靠經驗,偶爾還需要創造力。如果要求解的遞歸式與曾經見過的遞歸式類似,那麼猜測一個類似的解是合理的。另一種做出好的猜測的方法是先證明遞歸式較松的上界和下界,然後縮小不確定的範圍。
微妙的細節
有時可能猜出了遞歸式的漸近界,但證明失敗。,問題常常處在歸納假設不夠強,無法證出準確的界。這時,將它減去一個低階的項,可能會順利進行。
避免陷阱
改變變量
有時一個小得代數運算可以將一個未知的遞歸式變成你所熟悉的形式。例如:

T(n)=2T(n)+lgn

令m = lg n,得到
T(2m)=2T(2m/2)+m

重命名S(m)=T(2m) ,得到:
S(m)=2S(m/2)+m

得到S(m)=O(mlgm) .再轉回T(n),得到
T(n)=T(2m)=S(m)=O(mlgm)=O(lgnlglgn)
.

4.4 用遞歸樹方法求解遞歸式

  在遞歸樹中,每個節點表示一個單一子問題的代價,子問題對應某次遞歸函數的調用。我們將樹中每層中的代價求和,得到每層代價,然後將所有層的代價求和,得到所有層次的遞歸調用的總代價。
  遞歸樹最適合用來生成好的猜測,然後即可用代入法來驗證猜測是否正確。

4.5 用主方法求解遞歸式

  主方法爲如下形式的遞歸式提供了一種“菜譜”式的求解方法
  T(n) = aT(n/b) + f(n)
  其中,a>=1和b>1是常數,f(n)是漸近正函數。它描述的是這樣一種算法的運行時間:它將規模爲n的問題分解爲a個子問題,每個子問題規模爲n/b,其中a和b都是正常數。a個子問題遞歸地進行求解,每個花費時間T(n/b)。函數f(n)包含了問題分解和子問題合併的代價。
主定理
  主方法依賴於下面的定理:
  定理 4. 1(主定理) 令a>=1和b>1是常數,f(n)是一個函數,T(n)是定義在非負整數上的遞歸式:
  T(n) = aT(n/b) + f(n)
 其中,我們將n/b解釋爲n/bn/b 。那麼T(n)有如下漸近界:
 1. 若對某個常數ε>0有f(n)=O(nlogbaε ),則T(n)=Θ(nlogba ).
 2. 若f(n)=Θ(nlogba ),則T(n)=Θ(nlogbalgn ).
 3. 若對某個常數ε>0有f(n)=Ω(nlogba+ε ),且對某個常數c<1和所有足夠大的n有af(n/b)<=cf(n),則T(n)=Θ(f(n))。
 對於三種情況的每一種,我們將函數f(n)與函數nlogba 進行比較。直覺上,兩個函數較大者決定了遞歸式解。若函數nlogba 更大,如情況1,則解爲T(n)=Θ(nlogba)若函數f(n)更大,如情況3,則解爲T(n)=Θ(f(n))。若兩個函數大小相當,如情況 2,則乘上一個對數因子,解爲T(n)=Θ(nlogbalgn) =Θ(f(n)lgn)
  注意,這三種情況並未覆蓋f(n)的所有可能性。情況1和情況2之間有一定的間隙,f(n)可能小於nlogba 但不是多項式意義上的小於。類似地,情況2和情況3之間也有一定間隙,f(n)可能大於nlogba 但不是多項式意義上的大於。如果函數f(n)落在這兩個間隙中,後者情況3中要求的正則表達式不成立,就不能使用主方法來求解遞歸式。
使用主方法
  使用主方法很簡單,我們只需確定主定理的哪種情況成立,即可得到解。
  例1:T(n)=9T(n/3)+n
    a=9,b=3,f(n)=n.因此nlogba=Θ(n2) .因此可以應用主定理的情況1,從而得到解T(n)=Θ(n2) .
  例2:T(n)=T(2n/3)+1
    a=1,b=3/2,f(n)=1.因此nlogba=Θ(1) ,因此應用情況2,從而得到解T(n)=Θ(lg n).
  例3:T(n)=3T(n\4)+nlg n
    a=3,b=4,f(n)=nlg n,因此nlogba=nlog43=O(n0.793) ,由於f(n)=Ω(nlog43+ε ).應用情況3,遞歸式的解爲T(n)=Θ(nlg n)。
  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章