1. 程式人生 > >動態規劃--鋼條切割收益最大化問題

動態規劃--鋼條切割收益最大化問題

動態規劃–鋼條切割收益最大化問題

這個是《演算法導論》中動態規劃一章的問題

問題:

對應給定長度n米的鋼條,將其切割成k段(或不切k=1)出售,使收益最大,其中長度為i米的鋼條,其出售價格為pip_i

分析:

假設鋼條被切成k段,每段的長度分別為i1i_1,i2i_2,i3i_3,…,iki_k時,鋼條的收益最大,此時鋼條長度為 n=i1+i2+i3+...+ikn=i_1 + i_2 + i_3 + ... + i_k

最大收益為 rn=pi1+pi2+pi3+...+pikr_n=p_{i_1}+p_{i_2}+p_{i_3}+...+p_{i_k}

假設鋼條被切成兩段,要是收益最大,則這兩段必須以最大收益再進行切割,即 ri+rnir_i+r_{n-i}

那麼長度為n的鋼條的最大收益為 rn=max(pn,r1+rn1,r2+rn2,r3+rn3,...,rn1+r1)r_n=max(p_n,r_1+r_{n-1},r_2+r_{n-2},r_3+r_{n-3},...,r_{n-1}+r_1) 其中n>=1,pnp_n對應長度為n的鋼條,即不切割直接出售。

此時依然是求解最大收益問題,但問題規模更小了。

上邊的問題可以使用更簡單的求解方法,即不管鋼條如何切割, 左邊一定會有一段,假設左邊的第一段長度為i(i可以等於n),則右邊的長度為n-i,要使左邊第一段長度為i時的鋼條的收益最大,則要求右邊n-i長度的鋼條的收益最大,即為rnir_{n-i}, 此時鋼條的收益為 pi+rnip_i+r_{n-i} 則長度為n的鋼條的最大收益為 rn=max(p1+rn1,p2+rn2,p3+rn3,...,pn1+r1)r_n=max(p_1+r_{n-1},p_2+r_{n-2},p_3+r_{n-3},...,p_{n-1}+r_1)

其中1<=i<=n, 從上邊的分析可以看出我們求解規模為n的原問題,可以先求解形式完全一樣,規模為n-i的子問題。 故鋼條問題滿足最優子結構性質: 問題的最優解由相關子問題的最優解組合而成,而這些子問題可以獨立求解。

現在使用python對上述問題求解,

遞迴方法

# 遞迴法
def cutRod(p,n):
    if not n:
        return 0
    qlist = []
    for i in range(1,n+1):
        qlist.append(p[i]+cutRod(p,n-i))
    return max(qlist)

設T(n)表示求長度為n的鋼條收益時cutRod函式呼叫的次數,分析上述程式碼,很容易得到 T(n)=1+T(1)+T(2)+...+T(n1)T(n)=1+T(1)+T(2)+...+T(n-1) 使用數學歸納法,很容易證明 T(n)=2nT(n)=2^n 故利用上述遞迴法,函式cutRod的執行時間為n的指數函式

帶備忘錄的自頂向下方法

# 帶備忘錄自頂向下法
def medoizedCutRod(p,n):
    r=[-100 for i in range(0,n+1)]
    return memoizedCutRodAux(p,n,r)
       
def memoizedCutRodAux(p,n,r):
    if r[n]>=0:
        return r[n]
    if n==0:
        q=0
    else:
        q = -100
        for i in range(1,n+1):
            q = max(q,p[i]+memoizedCutRodAux(p,n-i,r))
    r[n]=q
    return q

自底向上法

# 自底向上法
def bottomUpCutRod(p,n):
    r=[0 for i in range(0,n)]
    for j in range(1,n+1):#長度為j的鋼條
        q = -100
        for i in range(1,j+1):#第一段為i的切割方案
            q = max(q,p[i]+r[j-i])
        r[j] = q
    return r[n]

自頂向下法和自底向上法有相同的執行漸進時間,他們都是對相同的子問題只求解一次,分析可以得到他們時間複雜度為Θ(n2)Θ(n^2),區別在於自底向上法沒有函式的遞迴呼叫,具有更小的係數。

切割方案

上述方法只是求出了最大收益,但沒有給出最大收益的切割方案,現在對自底向上法對進行修改,以儲存得到最大收益的切割方案

def extendedBottomUpCutRod(p,n):
    r=[0 for i in range(0,n+1)]
    s=[0 for i in range(0,n+1)]#儲存長度為j的鋼條最優切割方案的第一段長度
    for j in range(1,n+1):#長度為j的鋼條
        q = -100
        for i in range(1,j+1):#第一段為i的切割方案
            if q < p[i]+r[j-i]:
                q = p[i]+r[j-i]
                s[j] = i
        r[j] = q
    return (r,s)
    
def printCutRodSolution(p,n):
    (r,s) = extendedBottomUpCutRod(p,n)
    while n:
        print(s[n])#輸出切割方法
        n = n - s[n]

測試

全部測試程式碼如下:

# 出售價格
p={1:2,2:5,3:8,4:9,5:10,6:17,7:17,8:20,9:24,10:30}

# 遞迴法
def cutRod(p,n):
    if not n:
        return 0
    qlist = []
    for i in range(1,n+1):
        qlist.append(p[i]+cutRod(p,n-i))
    return max(qlist)

# 帶備忘錄自頂向下法
def medoizedCutRod(p,n):
    r=[-100 for i in range(0,n+1)]
    return memoizedCutRodAux(p,n,r)
       
def memoizedCutRodAux(p,n,r):
    if r[n]>=0:
        return r[n]
    if n==0:
        q=0
    else:
        q = -100
        for i in range(1,n+1):
            q = max(q,p[i]+memoizedCutRodAux(p,n-i,r))
    r[n]=q
    return q

# 自底向上法
def bottomUpCutRod(p,n):
    r=[0 for i in range(0,n+1)]
    for j in range(1,n+1):#長度為j的鋼條
        q = -100
        for i in range(1,j+1):#第一段為i的切割方案
            q = max(q,p[i]+r[j-i])
        r[j] = q
    #print(r)
    return r[n]

# 輸出切割方案和最大收益
def extendedBottomUpCutRod(p,n):
    r=[0 for i in range(0,n+1)]
    s=[0 for i in range(0,n+1)]#儲存長度為j的鋼條最優切割方案的第一段長度
    for j in range(1,n+1):#長度為j的鋼條
        q = -100
        for i in range(1,j+1):#第一段為i的切割方案
            if q < p[i]+r[j-i]:
                q = p[i]+r[j-i]
                s[j] = i
        r[j] = q
    return (r,s)
    
def printCutRodSolution(p,n):
    (r,s) = extendedBottomUpCutRod(p,n)
    print("最大收益:",r[n])
    print("切割方案:",end=" ")
    while n:
        print(s[n],end=" ")#輸出切割方法
        n = n - s[n]

if __name__ == "__main__":
    #鋼條長度
    n = 9
    r = cutRod(p,n)
    print("最大收益:",r)
    print("****************")
    
    r = medoizedCutRod(p,n)
    print("最大收益:",r)
    print("****************")
    
    r = bottomUpCutRod(p,n)
    print("最大收益:",r)
    print("****************")
    
    printCutRodSolution(p,n)

執行結果: 最大收益: 25

最大收益: 25

最大收益: 25

最大收益: 25 切割方案: 3 6

總結

動態規劃問題應具備兩個要素:最優子結構和子問題重疊 最優子結構: 問題的最優解包含了子問題的最優解 (或問題的最優解由相關子問題的最優解組合而成,而這些子問題可以獨立求解)。 最問題重疊: 問題的遞迴演算法會反覆求解相同的問題 (注意:分治演算法的解也由相關子問題的解構成,但其每次遞迴都產生新的子問題,不具備子問題的重疊性)。