1. 程式人生 > >《演算法導論》動態規劃 -------python 鋼條切割問題

《演算法導論》動態規劃 -------python 鋼條切割問題

1. 鋼條切割問題:一段長為 n 的鋼條和一個價格表 pi (i=1,2,3,4,...,n),求切割方案,使得銷售收益 Rn 最大。

長度 i 1 2 3 4 5 6 7 8 9 10
價格 pi 1 5 8 9 10 17 17 20 24 30

長度為 n 的鋼條有 2^(n-1) 種切割方案(因為在距離鋼條左端i(i=1,2,,3,...,n-1)處,我們總是可以選擇切割或者不切割)

一個最優切割方案將鋼條切為 k (1≤k≤n)段,最優切割方案為:n = i1 + i2 + i3 +...+ ik

切割的長度分別為:i1、i2 、i3 、...、ik,得到的最大收益:Rn = pi1  +  pi2  +  pi3  +...+  pik

2. 問題分析

    首先可以將鋼條分割為 i n-i 兩段,求這兩段的最優收益 RiR(n-i) (每種方案的最優收益為兩段的最優收益之和)。由於無法確定哪種方案(i 取何值時)會獲得最優收益,我們必須考慮所有的i,選取其中收益最大者。若直接出售鋼條會獲得最大收益,可以選擇不做任何切割。最優切割收益公式:Rn = max(pn,R1+Rn-1,R2+Rn-2,...,Rn-1+R1)。當完成首次切割後,我們可以將兩段鋼條( i

n-i)看成兩個獨立的鋼條切割問題例項,通過組合兩個相關子問題的最優解,構成原問題的最優解。

    一種相似但更為簡單的遞迴求解方法:將長度為 n 的鋼條分解為左邊開始一段,以及剩餘部分繼續分解的結果。簡化後的公式:Rn = max{1≤i≤n, (pi+Rn-i)}

3. 程式設計

方法1:自頂向下遞迴實現

缺點:輸入規模大時,程式執行時間會變得相當長

原因:反覆求解相同的子問題

#  自頂向下遞迴演算法實現
#  R[n] = max{1≤i≤n, (pi+R[n-i])}          時間複雜度為:2^n(指數函式)
def CutRod(p, n):  # 函式返回:切割長度為 n 的鋼條所得的最大收益
    if n == 0:
        return 0
    q = -1
    for i in range(1, n+1):
        q = max(q, p[i] + CutRod(p, n-i))
        '''
        tmp = p[i] + CutRod(p, n-i)
        if q < tmp:
            q = tmp
        '''
    return q
p=[0,1,5,8,9,10,17,17,20,24,30]   # 價格表,下標為對應的鋼條長度,如當鋼條長度為0時,p=0,即p[0]=0,p[2]=5
print("最大收益為:",CutRod(p,4))  #  最大收益為: 10

 假如 n=4時,求解函式用C(4)表示,下圖為 n=4 時 函式的遞迴展開,其中被淺綠色和黃色虛線方框圈住的部分為重複的部分。其中共有2^n個結點,2^(n-1)葉節點,程式時間複雜度為2^n(指數時間複雜度)

方法2:帶備忘錄的自頂向下遞迴

特點:對每個子問題只求解一次,並將結果儲存下來(隨後再次遇到此問題的解,只需查詢儲存的結果,不必重新計算),用記憶體空間來節省計算時間。

# 帶備忘錄的自頂向下法 -- 每個子問題只求解一次,並將之存放在陣列 r 中,以備用
def MemorizedCutRod(p, n):
    r=[-1]*(n+1)                          #  陣列初始化
    def MemorizedCutRodAux(p, n, r):
        if r[n] >= 0:
            return r[n]
        q = -1
        if n == 0:
            q = 0
        else:
            for i in range(1, n + 1):
                q = max(q, p[i] + MemorizedCutRodAux(p, n - i, r))
        r[n] = q
        return q
    return MemorizedCutRodAux(p, n, r),r
print("最大收益為:",MemorizedCutRod(p, 5))  # 最大收益為: (13, [0, 1, 5, 8, 10, 13])

方法3:自底向上法(動態規劃)

特點:任何子問題的求解都依賴於 更小子問題 的求解。對子問題規模進行排序,按由小到大的問題進行求解。當求解某個子問題時,他所依賴的更小的子問題都已求解完畢,結果已經儲存。

優點:時間複雜度在三種方法中最好

# 自底向上
def BottomUpCutRod(p, n):
    r = [0]*(n+1)
    for i in range(1, n+1):
        if n == 0:
            return 0
        q =0
        for j in range(1, i+1):
            q = max(q, p[j]+r[i-j])
            r[i] = q
    return r[n],r
p=[0,1,5,8,9,10,17,17,20,24,30]
print(BottomUpCutRod(p, 10))   #  (30, [0, 1, 5, 8, 10, 13, 17, 18, 22, 25, 30])