一,問題描述
給定一個背包,已知背包的最大承重為packageWeight,再給出若干件(numbers件)物品,已經每件物品的重量和對應的價值。
物品的重量存儲在weight[]數組中,物品的價值存儲在value[]數組中。
現在要求:應該挑選哪幾件物品,使得背包裝下最大的價值(注意:裝的物品的重量不能超過背包的承重)
(本文在最后打印出了裝入了哪幾件物品)
二,問題分析
這是一個典型的動態規划求解。對於每件物品而言,只有兩種選擇,要么選中它裝入背包;要么不選它。因此,這是一個0-1背包問題,而不是部分背包問題(只選這件物品的一部分)
關於部分背包問題,可參考:部分背包問題的貪心算法正確性證明
對於DP而言,關鍵是列出它的狀態方程,0-1背包問題的狀態方程與“硬幣找零”問題的狀態方程非常相似。關於硬幣找零,可參考:硬幣找零問題的動態規划實現
這里再重新分析一個0-1背包問題的狀態方程:
設 dp[i][j] 表示:在背包最大承重為 j 時,可選的物品件數有 i 件 的情況下,背包裝下的物品的最大價值。
dp[i-1][j-weight[i-1]]+value[i-1] 表示:當將第 i 件物品裝入背包時,背包還能承受的重量變成: j-weight[i-1] (weight[]數組下標0存儲第一件物品的重量)
由於第 i 件物品已經考慮了(將之裝入到背包了),故現在可裝入的物品 只有 i-1 件了。
dp[i-1][j]表示:不將第 i 件物品裝入背包。此時,本次選擇對背包的承重沒有影響,故 j 不變。由於第 i 件物品已經考慮了(不把它裝入背包),故現在可裝入的物品只有 i-1 件了。
由於我們要找能裝入背包的最大價值,在上面兩種情形下,哪種選擇的導致的價值最大,就選誰。從而狀態方程如下:--取二者中較大的那個。
dp[i][j]=max{dp[i-1][j-weight[i-1]]+value[i-1], dp[i-1][j]}
三,代碼分析:
假設第一行輸入兩個數: 背包的最大承重和物品的件數----packageWeight numbers
第二行輸入物品的重量,第三行輸入對應的物品的價值,格式如下:
10 5
2 2 6 5 4
6 3 5 4 6
1 String packInfo = null; 2 String weights = null; 3 String values = null; 4 while(scanner.hasNextLine()){ 5 packInfo = scanner.nextLine(); 6 int packageWeight = Integer.valueOf(packInfo.split(" ")[0]); 7 int numbers = Integer.valueOf(packInfo.split(" ")[1]); 8 9 weights = scanner.nextLine(); 10 values = scanner.nextLine(); 11 String[] weis = weights.split(" "); 12 String[] vals = values.split(" "); 13 //weight[]數組是從下標0開始存儲,索引0存儲第一件物品的重量 14 int[] weight = new int[numbers]; 15 int[] value = new int[numbers]; 16 17 for(int i = 0; i < numbers; i++) 18 { 19 weight[i] = Integer.valueOf(weis[i]); 20 value[i] = Integer.valueOf(vals[i]); 21 }
以上代碼解析輸入的情況。
1 int[][] dp = new int[numbers + 1][packageWeight + 1]; 2 3 //init 4 for(int i = 0; i <= numbers; i++) 5 dp[i][0] = 0; 6 for(int i = 0; i <= packageWeight; i++) 7 dp[0][i] = 0;
這段代碼對背包問題進行初始化。dp[i][0]=0 表示:背包最大承重為0,故不能裝物品,故裝入的物品最大價值也就是0了。
dp[0][j]=0 表示:可選的物品種類為0,背包的最大承重為 j 。都沒有物品可選,怎么裝?沒有物品裝啊。故最大價值也為0。
1 //dp[i][j] = max{dp[i-1][j], dp[i-1][j-weight[i-1]]+value[i-1]} 2 for(int i = 1; i <= numbers; i++) 3 { 4 for(int j = 1; j <= packageWeight; j++) 5 { 6 if(weight[i-1] > j)// 第i件物品的重量大於背包的承重 7 8 { 9 dp[i][j] = dp[i-1][j]; 10 continue; 11 } 12 //dp[i][j] = max{dp[i-1][j], dp[i-1][j-weight[i-1]]+value[i-1]} 13 if(dp[i-1][j] < dp[i-1][j-weight[i-1]] + value[i-1]) 14 dp[i][j] = dp[i-1][j-weight[i-1]] + value[i-1]; 15 else 16 dp[i][j] = dp[i-1][j]; 17 } 18 }
這段代碼,本質上就是狀態方程的實現。它以自底向上(從下標1開始求啊...)的方式求解DP問題。
既然,找出了能夠裝入的最大價值,那能不能知道裝入了哪些物品???
當然也是可以知道的。
1 //反向找出 選中的物品(哪些物品裝入到背包中了?) 2 int j= packageWeight; 3 for(int i = numbers;i>0;i--){ 4 if(dp[i][j]>dp[i-1][j]){ 5 System.out.print(i+" ");//輸出選中的物品的編號 6 j=j-weight[i-1]; 7 if(j<0) break; 8 }
第四行if語句成立時,說明將第 i 件物品裝入到背包中了,結果導致價值增大。第五行打印出裝入了哪幾件物品。
四,完整代碼實現

import java.util.Scanner; public class Main { public static void zeroOnePack(){ Scanner scanner = new Scanner(System.in); String packInfo = null; String weights = null; String values = null; while(scanner.hasNextLine()){ packInfo = scanner.nextLine(); int packageWeight = Integer.valueOf(packInfo.split(" ")[0]); int numbers = Integer.valueOf(packInfo.split(" ")[1]); weights = scanner.nextLine(); values = scanner.nextLine(); String[] weis = weights.split(" "); String[] vals = values.split(" "); //weight[]數組是從下標0開始存儲,索引0存儲第一件物品的重量 int[] weight = new int[numbers]; int[] value = new int[numbers]; for(int i = 0; i < numbers; i++) { weight[i] = Integer.valueOf(weis[i]); value[i] = Integer.valueOf(vals[i]); } int[][] dp = new int[numbers + 1][packageWeight + 1]; //init for(int i = 0; i <= numbers; i++) dp[i][0] = 0; for(int i = 0; i <= packageWeight; i++) dp[0][i] = 0; //dp[i][j] = max{dp[i-1][j], dp[i-1][j-weight[i-1]]+value[i-1]} for(int i = 1; i <= numbers; i++) { for(int j = 1; j <= packageWeight; j++) { if(weight[i-1] > j)// 第i件物品的重量大於背包的承重 { dp[i][j] = dp[i-1][j]; continue; } //dp[i][j] = max{dp[i-1][j], dp[i-1][j-weight[i-1]]+value[i-1]} if(dp[i-1][j] < dp[i-1][j-weight[i-1]] + value[i-1]) dp[i][j] = dp[i-1][j-weight[i-1]] + value[i-1]; else dp[i][j] = dp[i-1][j]; } } System.out.println(dp[numbers][packageWeight]);//輸出背包能夠裝的最大價值 //反向找出 選中的物品(哪些物品裝入到背包中了?) int j= packageWeight; for(int i = numbers;i>0;i--){ if(dp[i][j]>dp[i-1][j]){ System.out.print(i+" ");//輸出選中的物品的編號 j=j-weight[i-1]; if(j<0) break; } }//end while } scanner.close(); } //hapjin's test case //10 5 //2 2 6 5 4 //6 3 5 4 6 public static void main(String[] args) { zeroOnePack(); } }
五,參考資料