01揹包問題
題目
有N件物品和一個容量爲W的揹包。第i件物品的體積是w[i],價值是v[i]。求解將哪些物品裝入揹包可使價值總和最大。
基本思路
特點是:每種物品僅有一件,可以選擇放或不放。
用子問題定義狀態:即dp[i][j]表示前i件物品恰放入一個容量爲j的揹包可以獲得的最大價值。
則其狀態轉移方程便是:
01揹包模板代碼:
//N爲物品數量
//W爲揹包總體積
//weights[]爲每個物品的體積
//values[]爲每個物品的價值
public int zeroOneKnapsack(int N, int W, int[] weights, int[] values){
int[][] dp = new int[N+1][W+1];
for(int i = 1; i <= N; i++){
int w = weights[i-1], v = values[i-1];//每個物品的體積和價值
for(int j = 1; j <= W; j++){
if(j >= w){
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-w]+v);
}else{
dp[i][j] = dp[i-1][j];
}
}
}
return dp[N][W];
}
我們發現 dp[i][j] 只與 dp[i-1][j] 和 dp[i-1][j-w]有關,也就是當前行的某個值只與前一行的兩個值有關。
那麼我們只用一維數組記錄,空間優化後的狀態轉移方程:
01揹包優化後的模板代碼:
//N爲物品數量
//W爲揹包總體積
//weights[]爲每個物品的體積
//values[]爲每個物品的價值
public int zeroOneKnapsack(int N, int W, int[] weights, int[] values){
int[] dp = new int[W+1];
for(int i = 1; i <= N; i++){
int w = weights[i-1], v = values[i-1];//每個物品的體積和價值
for(int j = W; j >= w; j--){
dp[j] = Math.max(dp[j], dp[j-w]+v);
}
}
return dp[W];
}
爲什麼空間優化後的01揹包代碼揹包空間要逆序遍歷?(我們來舉一例子)
dp[j]是代表dp[i][j]和dp[i-1][j], 重點在dp[j-w],代表dp[i][j-w]和dp[i-1][j-w]。
在揹包空間順序遍歷的話,若我們要計算dp[j], 由公式dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-w]+v);可知,dp[j](實際是dp[i][j])與dp[i-1][j], dp[i-1][j-w]有關,但是dp[i-1][j-w]實際上已經被dp[i-1][j-w]給覆蓋了。
舉個例子:
N = 4, W = 4
下面爲每個物品的體積和價值:
由狀態轉移方程
可得:
dp[3][4] = max{dp[2][4], dp[2][1]+4}; 注意dp[2][1],如果是順序的話,會被dp[3][1]給覆蓋掉。如果是逆序則不會。
推薦:
揹包九講1——01揹包問題的理解(Java圖解)
https://blog.csdn.net/caigen0001/article/details/106698380
揹包九講2——完全揹包問題的理解(Java圖解)
https://blog.csdn.net/caigen0001/article/details/106711469
揹包九講3——多重揹包問題的理解(Java圖解)
https://blog.csdn.net/caigen0001/article/details/106720118
揹包九講4——二維揹包問題的理解(Java圖解)
https://blog.csdn.net/caigen0001/article/details/106720280
參考資料:
揹包九講 https://github.com/tianyicui/pack
揹包九講專題 https://www.bilibili.com/video/BV1qt411Z7nE?from=search&seid=6165804124910947817