動態規劃--鋼條切割收益最大化問題
動態規劃–鋼條切割收益最大化問題
這個是《演算法導論》中動態規劃一章的問題
問題:
對應給定長度n米的鋼條,將其切割成k段(或不切k=1)出售,使收益最大,其中長度為i米的鋼條,其出售價格為。
分析:
假設鋼條被切成k段,每段的長度分別為,,,…,時,鋼條的收益最大,此時鋼條長度為
最大收益為
假設鋼條被切成兩段,要是收益最大,則這兩段必須以最大收益再進行切割,即
那麼長度為n的鋼條的最大收益為 其中n>=1,對應長度為n的鋼條,即不切割直接出售。
此時依然是求解最大收益問題,但問題規模更小了。
上邊的問題可以使用更簡單的求解方法,即不管鋼條如何切割, 左邊一定會有一段,假設左邊的第一段長度為i(i可以等於n),則右邊的長度為n-i,要使左邊第一段長度為i時的鋼條的收益最大,則要求右邊n-i長度的鋼條的收益最大,即為, 此時鋼條的收益為 則長度為n的鋼條的最大收益為 其中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函式呼叫的次數,分析上述程式碼,很容易得到 使用數學歸納法,很容易證明 故利用上述遞迴法,函式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]
自頂向下法和自底向上法有相同的執行漸進時間,他們都是對相同的子問題只求解一次,分析可以得到他們時間複雜度為,區別在於自底向上法沒有函式的遞迴呼叫,具有更小的係數。
切割方案
上述方法只是求出了最大收益,但沒有給出最大收益的切割方案,現在對自底向上法對進行修改,以儲存得到最大收益的切割方案
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
總結
動態規劃問題應具備兩個要素:最優子結構和子問題重疊 最優子結構: 問題的最優解包含了子問題的最優解 (或問題的最優解由相關子問題的最優解組合而成,而這些子問題可以獨立求解)。 最問題重疊: 問題的遞迴演算法會反覆求解相同的問題 (注意:分治演算法的解也由相關子問題的解構成,但其每次遞迴都產生新的子問題,不具備子問題的重疊性)。