1. 程式人生 > >演算法導論 — 15.1 鋼條切割

演算法導論 — 15.1 鋼條切割

筆記

       本節給出一個尋找鋼條最優切割方案的問題。公司購買長鋼條,將其切割為短鋼條出售。為簡化分析,假設切割過程本身沒有成本,並且切割下來的短鋼條長度都為一英寸的整數倍。下表給出了不同長度的鋼條的價格。

       鋼條切割問題:給定一根長度為n英寸的長鋼條,求最優切割方案,使得銷售收益最大。注意,最優方案也有可能是完全不用切割。

      長度為n英寸的鋼條有種切割方案,因為在距離鋼條左端i (i = 1, 2, … , n-1)英寸處,我們總是可以選擇切割或不切割。但是在實際求解過程中,可以不用遍歷這種切割方案,而採用某種方法可以將該問題分解為規模更小的子問題,以下是求解該問題的方法:

  • 我們將鋼條從左端切下長度為 的一段,其中i =1, 2, … , n,有n種切法,我們對這一段不再進行切割,該段的銷售收益為
  • 而右端剩下的長度為n-i,對這一段再進行切割,這是一個規模更小的子問題,其銷售收益為

       上圖比較直觀地展示了求解方法。顯然,我們可以得到最優收益

       由上面的過程,我們引出了動態規劃的第一個基本特點:所求解的問題滿足最優子結構,問題可以分解為規模更小的子問題,問題的最優解依賴於子問題的最優解,並且這些子問題可以獨立求解。

       根據上面的公式,我們自然而然地可以用遞迴的方式寫出程式碼。

     

       下圖顯示了CUT-ROD在n

= 4時的遞迴樹。可以看到,CUT-ROD會重複以相同的引數呼叫多次。例如在下圖中,CUT-ROD對n = 2重複呼叫了2次,對n =1重複呼叫了4次,而對n = 0重複呼叫了7次。

       我們來分析CUT-ROD的執行時間。令T(n)表示引數為n時CUT-ROD的呼叫次數。T(n)等於遞迴樹中根為n的子樹中的結點總數,因此有

       令T(0) = 1,可以求解得到。所以,CUT-ROD有一個指數級別的時間複雜度。顯然,CUT-ROD的時間效率是很低的,原因是相同的子問題被重複多次求解了。

       為了得到一個時間效率更高的演算法,我們可以對每個子問題只求解一次,並將結果儲存下來。如果遞迴過程中再次需要子問題的解,只需要查詢已儲存的結果,而不必重新計算。由此我們又引出了動態規劃的第二個基本特點:相同的子問題只需要求解一次,如果子問題的解會被多次引用,可以將子問題的解儲存起來。

下面給出相應的程式碼實現。

      

       下面給出了另一種方法。與上文的“自上而下”的遞迴方式不同,這種方法採用的是“自下而上”的方式。任何子問題的解都只依賴於規模更小的子問題。我們可以將子問題按照規模由小到大的順序進行求解。當求解某個子問題時,它所依賴的那些規模更小的子問題都已求解完畢,並且結果已經儲存。每個子問題也只需要求解一次。

      

       BOTTOM-TO-UP-CUT-ROD的核心實際上是一個巢狀迴圈,不難得出它的執行時間為。相比於程式碼一的指數增長的執行時間,BOTTOM-TO-UP-CUT-ROD的執行效率大為提高。

       上文給出的程式碼只能得到最優收益,而不能得到最優切割方案本身。我們需要在演算法計算最優收益的同時,記錄下最優的切割方案。下面給出程式碼。

      

練習

15.1-1 由公式(15.3)和初始條件T(0) = 1,證明公式(15.4)成立。

       解

       該問題實際上是要分析程式碼一的執行時間。

       用數學歸納法。初始條件取n = 0,有,顯然命題對於初始條件是成立的。

       現在考慮n > 0的情況。假設命題對所有1 ~ n-1的情況都成立,那麼有。命題得證。

15.1-2 舉反例證明下面的“貪心”策略不能保證總是得到最優切割方案。定義長度為 的鋼條的價格密度為,即每英寸的價格。貪心策略將長度為 的鋼條切割下長度為 (1 ≤ in)的一段,其價格密度最高。接下來繼續使用相同的策略切割長度為 n-的剩餘部分。

       解

       還是以上文的價格表為例,可以算出每種長度的價格密度,如下表所示。

       如果原始的鋼條長度為4。按照貪心策略,先切下長度為3的一段,因為長度不超過4的鋼條中,長度為3的鋼條的價格密度最高;然後剩下一段長度為1。這種切割方式的收益為。而實際上最優切割方案是切割為兩段長度為2的鋼條,所獲得的收益為10。因此,貪心策略是不可靠的。

15.1-3 我們對鋼條切割問題進行一點修改,除了切割下的鋼條段具有不同的價格 外,每次切割還要付出固定的成本c。這樣,切割方案的收益就等於短鋼條的價格之和減去切割成本。設計一個動態規劃演算法解決修改後的鋼條切割問題。

       解

       該問題仍然滿足上文提到的最優子結構。只需對演算法稍加修改,即在計算每種方案的收益時減去切割成本。因此,最優收益公式變成了

       需要注意的是,不切割的方案不會有切割成本。下面的程式碼是在上文程式碼三的基礎上修改的。

      

15.1-4 修改MEMOIZED-CUR-ROD,使之不僅返回最優收益值,還返回切割方案。

       解

      

15.1-5 斐波那契數列可以用遞迴式(3.22)定義。設計一個O(n)的時間的動態規劃演算法計算第n個斐波那契數。

       解

       最簡單的方法是直接採用遞迴的方式來計算Fn,如下面的程式碼所示。

      

       該演算法的執行時間滿足遞迴式T(n) = T(n-1)+ T(n-2),假設初始條件為T(0) = 1,T(1) = 1。我們寫出從n到2的執行時間的遞迴式,得到以下方程組。

       將方程組中的每個方程相加,消去抵消項,得到

       這個遞迴式與習題15.1-1中要求解的遞迴式有幾乎相同的形式,可以推斷。因此,直接採用遞迴的方式來計算Fn,時間效率是很低下的。效率低下的原因也是重複求解了相同規模的子問題。為了提高時間效率,可以借鑑鋼條切割問題中的自下而上的求解方法。

     

       該演算法的核心是從2到 的迴圈迭代,這反映了自下而上的求解方法。每次迭代 i,只需要用到前兩個元素,即第 i-1 個元素和第 i-2 個元素。因此,僅需要用兩個區域性變數  和  來儲存第 i-1 個元素和第 i-2 個元素,並且每次迭代後更新這兩個區域性變數,而無需儲存從0到 的每個元素。顯然,這個演算法的時間複雜度為O(n)。

       本節相關的code可以到github上下載。