動態規劃問題首先是分解原始問題爲相同形式的子問題,是利用重複的子問題不再重新計算的算法。
鋼鋸切割是其中一個典型代表,子問題形式爲左邊不切割,右邊的進行切割,首先是樸素的遞歸形式,時間複雜度是2的n次方,
代碼1
#-*- coding: utf8 -*- import time '''鋼條切割問題的自頂向下版本,直接進行遞歸 p是長度對應的價格表 n是現在的鋼條長度 求解最大利潤''' def CUT_ROD(p, n): if n is 0: return 0 q = 0 for i in xrange(1, n+1): q = max(q, p[i]+CUT_ROD(p, n-i)) return q p = {0:0,1:1,2:5,3:8,4:9,5:10,6:17,7:17,8:20,9:24,10:30} def main(): n = 24 max_key = max(p.keys()) if n > max_key: for i in xrange(max_key+1, n+1): p[i] = 0 #原來的沒有p[i],否則報keyerror p[i] = CUT_ROD(p, i) print n, p[n] if __name__ == '__main__': begin = time.time() main() print 'total run time', time.time()-begin
用時:
>>> 24 70 total run time 35.2200000286
此方式效率看得出是很低的,有兩種方式可以優化上面的方案:
加入備忘機制,就是中途將計算的子問題的最優解存儲起來
代碼2
#-*- coding: utf8 -*- import time ''' “自頂向下”帶備忘,是樸素遞歸帶備忘的形式 top-down with memoization ''' def MEMOIZED_CUT_ROD(p, n): r = [] #用來存儲長度爲n的最大收益,就是所有子問題的解 for i in xrange(0, n+1): # 0 to n 初始化 r.append(-1) return MEMOIZED_CUT_ROD_AUX(p, n, r) def MEMOIZED_CUT_ROD_AUX(p, n, r): if r[n]>=0: return r[n] #這裏就是已經存在的長度爲n的最大收益,如果存在則直接取出,不再計算 if n is 0: q = 0 else: q = -1 for i in xrange(1, n+1): # 1 to n q = max(q, p[i] + MEMOIZED_CUT_ROD_AUX(p, n-i, r)) r[n]=q return q p = {0:0,1:1,2:5,3:8,4:9,5:10,6:17,7:17,8:20,9:24,10:30} def main(): n = 24 max_key = max(p.keys()) if n > max_key: for i in xrange(max_key+1, n+1): p[i] = 0 #原來的沒有p[i],否則報keyerror print MEMOIZED_CUT_ROD(p, n) if __name__ == '__main__': begin = time.time() main() print 'total run time', time.time()-begin
用時:
>>> 70 total run time 0.0120000839233
看出優化是很有效的
第二種方式是自底向上的尋找最優解,更加簡單而且執行速度更快:
代碼3
#-*- coding: utf8 -*- import time ''' 自底向上法 bottom-up method ''' def BOTTOM_UP_CUT_ROD(p, n): r = [] #用來存儲長度爲n的最大收益,就是所有子問題的最優解 for i in xrange(0, n+1): # 0 to n 初始化 r.append(-1) r[0] = 0 #長度爲0, 收益爲0 for j in xrange(1, n+1): q = -1 for i in xrange(1, j+1): q = max(q, p[i]+r[j-i]) r[j] = q return r[n] p = {0:0,1:1,2:5,3:8,4:9,5:10,6:17,7:17,8:20,9:24,10:30} def main(): n = 24 max_key = max(p.keys()) if n > max_key: for i in xrange(max_key+1, n+1): p[i] = 0 #原來的沒有p[i],否則報keyerror print BOTTOM_UP_CUT_ROD(p, n) if __name__ == '__main__': begin = time.time() main() print 'total run time', time.time()-begin
用時:
>>> 70 total run time 0.0110001564026
第二種方式更加簡潔,且沒有遞歸,因爲一程序都有遞歸調用的最大深度的限制,兩者漸進時間一致,但是因爲少了遞歸,第二種方式的係數小一些
稍加修改上面的代碼,使之不僅返回最大收益,而且打印出最優解
#-*- coding: utf8 -*- import time ''' 輸出最優解的自底向上法 bottom-up method ''' def EXTENDED_BOTTOM_UP_CUT_ROD(p, n): r, s = [], [] # r用來存儲長度爲n的最大收益,就是所有子問題的最優解 # s存儲對長度爲n的鋼條切割的最優解對應的第一段鋼條的切割長度 for i in xrange(0, n+1): # 0 to n 初始化 r.append(-1) s.append(0) r[0] = 0 #長度爲0, 收益爲0 for j in xrange(1, n+1): q = -1 for i in xrange(1, j+1): if q < p[i]+r[j-i]: q = p[i]+r[j-i] s[j] = i r[j] = q return r, s def PRINT_CUT_ROD_SOLUTION(p, n): r, s = EXTENDED_BOTTOM_UP_CUT_ROD(p, n) print 'max revenue is', r[n] while n>0: print s[n] n = n-s[n] p = {0:0, 1:1, 2:5, 3:8, 4:9, 5:10, 6:17, 7:17, 8:20, 9:24, 10:30} def main(): n = 9 max_key = max(p.keys()) if n > max_key: for i in xrange(max_key+1, n+1): p[i] = 0 #原來的沒有p[i],否則報keyerror PRINT_CUT_ROD_SOLUTION(p, n) if __name__ == '__main__': begin = time.time() main() print 'total run time', time.time()-begin
輸出:
>>> max revenue is 25 3 6 total run time 0.0220000743866