0-1揹包問題:動態規劃 python 空間優化

0-1揹包問題總述

0-1揹包問題是最簡單的問題,此外還要完全揹包問題、多重揹包問題、混合揹包問題、二維費用揹包問題、分組揹包問題等等。好的參考資料可以見《揹包問題九講》:https://www.kancloud.cn/kancloud/pack/70124

其中0-1揹包問題是最基本的問題,其問題描述如下:

給出n個珍珠的體積v[i]和其價值price[i],將他們裝入一個大小爲C的揹包,最多能裝入的總價值有多大?

之所以叫0-1揹包,就是因爲n個物品,每個只有一件,只有裝包/不裝包兩種狀態,即0-1問題。

解決該問題的方法爲動態規劃(Dynamic Programming),動態規劃單聽這個詞覺得很龐大,其實可以說是對遞歸的一種優化,遞歸是不管需不需要都進行重新計算,其計算複雜度極高,動態規劃用數據來存儲其之前的狀態,當進行下一步計算的時候直接調用不需要重新計算。那麼問題來了,動態規劃需要確定兩個點:狀態、狀態轉移方式。這裏可以參考很多資料,不再仔細敘述。

狀態轉移方式:dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]] +price[i]);

最後計算出結果後,還要把哪些東西放入揹包可以打印出來,這個利用倒循化進行遍歷,如果後一個物品放入後價值高,則確定是要放入的;然後更新當前的體積。

二維數據解決0-1揹包問題

二維空間的python代碼如下:

# 將珠寶裝入揹包,珠寶Jewelry有體積、價值(v,price)
# 揹包總容量爲C,問題:如何將最有價值的珠寶裝進去
import numpy as np
def pack():
    C=10
    num=5
    v =   [4,3,5,2,5]
    price=[9,6,1,4,1] # 初始定義好的東西
    sum=np.zeros((num+1,C+1)) # 相當於申請二維的空間
    for jew in range(num+1): # 珠寶個數
        for c in range(C+1): # 容量大小
            if(jew==0):    # 如果沒有裝入珠寶
                sum[jew][c]=0
            else:
                sum[jew][c]=sum[jew-1][c]
            if(jew>0 and c>v[jew-1]):
                # d[i][j] >?= d[i - 1][j - v[i - 1]] + price[i - 1];
                sum[jew][c]=max(sum[jew-1][c],sum[jew-1][c-v[jew-1]]+price[jew-1])
    print("the max price:",sum[num][C])

    # 記錄最終的裝入揹包的珠寶,並打印 pack[]
    pack=np.zeros((num))
    volume = C
    print(pack)
    for jew in range(num,-1,-1):  # 倒敘遍歷(循環)
        if(sum[jew][volume]>sum[jew-1][volume]):  # 如果加入最後一個比前一個價值要高
            pack[jew-1]=1
            volume=volume-v[jew-1]

    print("珠寶裝包情況",pack)
    # print(C)
    # print(sum)

if __name__=="__main__":
    pack()

結果爲:

最後一個矩陣爲所有的記錄,打印出來方便理解。

空間優化:一維數據解決0-1揹包

從代碼以及動態規劃的角度可以很明顯的感覺到,時間複雜度是無法優化的,但當前狀態dp[i]只與前一個狀態dp[i-1]相關,所以其空間複雜度可以進一步優化。那麼其核心問題就變爲:如果只用一個數組dp[0...V],能不能保證第i次循環結束後dp[v]就是之前定義的狀態dp[i][v]呢?dp[i][v]是由dp[i-1][v]和dp[i-1][v-c[i]]兩個子問題遞推而來,能否保證在推dp[i][v]時(也即在第i次主循環中推f[v]時)能夠得到dp[i-1][v]和dp[i-1][v-c[i]]的值呢?事實上,在每次主循環中我們以v=V...0的逆序推dp[v],這樣才能保證推dp[v]時dp[v-c[i]]保存的是狀態dp[i-1][v-c[i]]的值。僞代碼如下:

for i  in 0 ... N
    for  v = V ... 0
        dp[v] = max{dp[v], dp[v-v[i]] + price[i]}

所以,其一維數據的代碼爲:

def find1array():
    C = 10  # 揹包總體積
    num = 5 # 物品個數
    v   =   [4, 3, 5, 2, 5] # 每個物品體積
    price = [9, 6, 1, 4, 1]  # 初始定義好的價格
    dp=[0 for i in range(C+1)] # 定義固定大小()的數組
    for i in range(num): # 從第i個物品開始遍歷
        for j in range(C,v[i]-1,-1): # 從容量開始往下遞減
            dp[j]=max(dp[j],dp[j-v[i]]+price[i])

    print("一維遞歸計算結果:",dp[C])

 

看後歡迎點贊。哈哈

 

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