動態規劃之鋼鋸切割問題

動態規劃問題首先是分解原始問題爲相同形式的子問題,是利用重複的子問題不再重新計算的算法。

鋼鋸切割是其中一個典型代表,子問題形式爲左邊不切割,右邊的進行切割,首先是樸素的遞歸形式,時間複雜度是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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章