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])
看後歡迎點贊。哈哈