算法導論 — 4.1 最大子數組問題

筆記

本節給出了分治法的一個例子。給定一個數組A[1..n]A[1..n],找出一個元素和爲最大的連續子數組A[i..j]A[i..j],其中1ijn1 ≤ i ≤ j ≤ n,稱這樣的子數組爲最大子數組。例如,下圖所示數組中,第88個元素到第1111個元素之間的子數組爲最大子數組。
  在這裏插入圖片描述
  求解最大子數組問題,最簡單的方法是暴力檢查所有的子數組,從中找出和爲最大的子數組。對於一個有nn個元素的數組,一共有Cn2+Cn1=n(n1)/2+n=Θ(n2)C_n^2+C_n^1=n(n-1)/2+n=Θ(n^2)個子數組。參考練習4.1-2可知,計算每個子數組的和只需要O(1)O(1)時間。因此暴力求解法的所花費的時間爲Θ(n2)Θ(n^2)
  除了暴力求解法,最大子數組問題還可以使用分治法求解,並且分治法具有更優的時間複雜度。假定要尋找子數組A[low..high]A[low..high]的最大子數組,我們從中央位置mid=(low+high)/2mid=⌊(low+high)/2⌋A[low..high]A[low..high]劃分爲兩個子數組,A[low..mid]A[low..mid]A[mid+1..high]A[mid+1..high]。於是,A[low..high]A[low..high]的任何一個子數組A[i..j]A[i..j]必然是以下三種情況之一:
  • 完全位於子數組A[low..mid]A[low..mid]中,即lowijmidlow ≤ i ≤ j ≤ mid
  • 完全位於子數組A[mid+1..high]A[mid+1..high]中,即mid<ijhighmid < i ≤ j ≤ high
  • 跨越了中央位置midmid,即lowimid<jhighlow ≤ i ≤ mid < j ≤ high
  根據以上分析,可以遞歸求解最大子數組問題。對於一個子數組A[low..high]A[low..high],首先尋找跨越中央位置的最大子數組,然後分別遞歸求解A[low..mid]A[low..mid]A[mid+1..high]A[mid+1..high]的最大子數組,比較這三種情況的最大子數組,從中選出元素和最大者作爲A[low..high]A[low..high]的最大子數組。
  分治法的關鍵在於尋找跨越中央位置的最大子數組。對於一個子數組A[low..high]A[low..high],任何跨越中央位置midmid的子數組必然都由兩個子數組A[i..mid]A[i..mid]A[mid+1..j]A[mid+1..j]組成,其中lowimidlow ≤ i ≤ mid並且mid<jhighmid < j ≤ high。因此,我們只需要找出形如A[i..mid]A[i..mid]和A[mid+1..j][mid+1..j]的最大子數組,然後將二者合併即可。下面給出尋找跨越中央位置的最大子數組的僞代碼。
  在這裏插入圖片描述
  接下來給出分治法求解最大數組問題的僞代碼。
   在這裏插入圖片描述
  要尋找數組A[1..n]A[1..n]的最大子數組,只需要調用FIND-MAXIMUM -SUBARRAY(A,1,n)(A, 1, n)即可。
  下面分析分治法求解最大子數組問題的時間複雜度。對於長度爲nn的數組,求解最大子數組的時間用T(n)T(n)表示。T(n)T(n)由三部分組成:
  • 遞歸求解子數組A[1..mid]A[1..mid]的最大子數組的時間T(n/2)T(n/2)
  • 遞歸求解子數組A[mid+1..n]A[mid+1..n]的最大子數組的時間T(n/2)T(n/2)
  • 求解跨越中央位置的最大子數組的時間,這一時間爲Θ(n)Θ(n)
  所以有遞歸式T(n)=2T(n/2)+Θ(n)T(n) = 2T(n/2) +Θ(n)。求解這個遞歸式,得到T(n)=Θ(nlgn)T(n) = Θ(nlgn)

練習

4.1-1AA的所有元素均爲負數時,FIND-MAXIMUM-SUBARRAY返回什麼?
  
  返回數值最大的那個負數,即絕對值最小的負數。
  
4.1-2 對最大子數組問題,編寫暴力求解方法的僞代碼,其運行時間應該爲Θ(n2)Θ(n^2)
  
  在這裏插入圖片描述
  
4.1-3 當你的計算機上實現最大子數組問題的暴力算法和遞歸算法。請指出多大的問題規模n0n_0是性能交叉點——從此之後遞歸算法將擊敗暴力算法?然後,修改遞歸算法的基本情況——當問題規模小於n0n_0時採用暴力算法。修改後,性能交叉點會改變嗎?
  

4.1-4 假定修改最大子數組問題的定義,允許結果爲空子數組,其和爲00。你應該如何修改現有算法,使它們能允許空子數組爲最終結果?
  
  先對整個數組遍歷一遍,檢查是否所有元素都爲負數。如果所有元素都爲負數,則算法輸出空子數組。如果數組中存在正數,則調用FIND-MAXIMUM –SUBARRAY求解。

4.1-5 使用如下思想爲最大子數組問題設計一個非遞歸的、線性時間的算法。從數組的左邊界開始,由左至右處理,記錄到目前爲止已經處理過的最大子數組。若已知A[1..j]A[1..j]的最大子數組,基於如下性質將解擴展爲A[1..j+1]A[1..j+1]的最大子數組:A[1..j+1]A[1..j+1]的最大子數組要麼是A[1..j]A[1..j]的最大子數組,要麼是某個形如A[i..j+1]A[i..j+1]的最大子數組(1ij+1)(1 ≤ i ≤ j+1)。在已知形如A[i..j]A[i..j]的最大子數組的情況下,可以在常數時間內找出形如A[i..j+1]A[i..j+1]的最大子數組。
  
  與分治法不同,這是典型的增量法。本題的關鍵在於:在已知以A[j]A[j]結尾的最大子數組的情況下,找出以A[j+1]A[j+1]結尾的最大子數組。假設以A[j]A[j]結尾的最大數組爲A[i..j](1ij)A[i..j] (1 ≤ i ≤ j)。分兩種情況:
  (1) 如果A[i..j]A[i..j]各元素之和sum{A[i..j]}>0{\rm sum}\{A[i..j]\} > 0,那麼以A[j+1]A[j+1]結尾的最大數組爲A[i..j+1]A[i..j+1]。這一點可以用反證法來說明。假設以A[j+1]A[j+1]結尾的最大數組爲A[k..j+1]A[k..j+1],其中1kj+11 ≤ k ≤ j+1並且kik ≠ i。又分兩種情況討論。
  1) 1kj1 ≤ k ≤ j:由於以A[j]A[j]結尾的最大子數組爲A[i..j]A[i..j],所以sum{A[k..j]}sum{A[i..j]}{\rm sum}\{A[k..j]\} ≤ {\rm sum}\{A[i..j]\},從而有sum{A[k..j+1]}sum{A[i..j+1]}{\rm sum}\{A[k..j+1]\} ≤ {\rm sum}\{A[i..j+1]\}。如果sum{A[k..j+1]}<sum{A[i..j+1]}{\rm sum}\{A[k..j+1]\} < {\rm sum}\{A[i..j+1]\},那麼A[k..j+1]A[k..j+1]肯定不是以A[j+1]A[j+1]結尾的最大數組,這與假設矛盾。如果sum{A[k..j+1]}=sum{A[i..j+1]}{\rm sum}\{A[k..j+1]\} = {\rm sum}\{A[i..j+1]\},那麼如果假設成立,即A[k..j+1]A[k..j+1]是以A[j+1]A[j+1]結尾的最大數組,那麼A[i..j+1]A[i..j+1]也同樣是以A[j+1]A[j+1]結尾的最大數組。
  2) k=jk = j:此時假設的以A[j+1]A[j+1]結尾的最大數組爲A[j+1]A[j+1]本身。由於sum{A[i..j]}>0{\rm sum}\{A[i..j]\} > 0,所以sum{A[i..j+1]}>A[j+1]{\rm sum}\{A[i..j+1]\} > A[j+1]。這說明A[j+1]A[j+1]本身肯定也不是以A[j+1]A[j+1]結尾的最大數組,這與假設矛盾。
  (2) 如果A[i..j]A[i..j]各元素之和sum{A[i..j]}0{\rm sum}\{A[i..j]\} ≤ 0,那麼A[j+1]A[j+1]結尾的最大數組爲A[j+1]A[j+1]本身。這一點同樣可以用反證法來說明,這裏就不贅述。
  下面給出該算法的僞代碼。
  在這裏插入圖片描述
  對於一個包含nn個元素的數組,該算法一共包含nn次迭代,每次迭代花費Θ(1)Θ(1)時間。因此,該算法的運行時間爲Θ(n)Θ(n)
  
  本節代碼鏈接:
  https://github.com/yangtzhou2012/Introduction_to_Algorithms_3rd/tree/master/Chapter04/Section_4.1

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