背包問題具體例子:假設現有容量10kg的背包,另外有3個物品,分別為a1,a2,a3。物品a1重量為3kg,價值為4;物品a2重量為4kg,價值為5;物品a3重量為5kg,價值為6。將哪些物品放入背包可使得背包中的總價值最大?
首先想到的,一般是窮舉法,一個一個地試,對於數目小的例子適用,如果容量增大,物品增多,這種方法就無用武之地了。
其次,可以先把價值最大的物體放入,這已經是貪婪算法的雛形了。如果不添加某些特定條件,結果未必可行。
最后,就是動態規划的思路了。先將原始問題一般化,欲求背包能夠獲得的總價值,即欲求前i個物體放入容量為m(kg)背包的最大價值c[i][m]——使用一個數組來存儲最大價值,當m取10,i取3時,即原始問題了。而前i個物體放入容量為m(kg)的背包,又可以轉化成前(i-1)個物體放入背包的問題。下面使用數學表達式描述它們兩者之間的具體關系。
表達式中各個符號的具體含義。
w[i] : 第i個物體的重量;
p[i] : 第i個物體的價值;
c[i][m] : 前i個物體放入容量為m的背包的最大價值;
c[i-1][m] : 前i-1個物體放入容量為m的背包的最大價值;
c[i-1][m-w[i]] : 前i-1個物體放入容量為m-w[i]的背包的最大價值;
由此可得:
c[i][m]=max{c[i-1][m-w[i]]+pi , c[i-1][m]}(下圖將給出更具體的解釋)
根據上式,對物體個數及背包重量進行遞推,列出一個表格(見下表),表格來自(http://blog.csdn.net/fg2006/article/details/6766384?reload) ,當逐步推出表中每個值的大小,那個最大價值就求出來了。推導過程中,注意一點,最好逐行而非逐列開始推導,先從編號為1的那一行,推出所有c[1][m]的值,再推編號為2的那行c[2][m]的大小。這樣便於理解。
思路厘清后,開始編程序,Java代碼如下所示:
public class BackPack { public static void main(String[] args) { int m = 10; int n = 3; int w[] = {3, 4, 5}; int p[] = {4, 5, 6}; int c[][] = BackPack_Solution(m, n, w, p); for (int i = 1; i <=n; i++) { for (int j = 1; j <=m; j++) { System.out.print(c[i][j]+"\t"); if(j==m){ System.out.println(); } } } //printPack(c, w, m, n); } /** * @param m 表示背包的最大容量 * @param n 表示商品個數 * @param w 表示商品重量數組 * @param p 表示商品價值數組 */ public static int[][] BackPack_Solution(int m, int n, int[] w, int[] p) { //c[i][v]表示前i件物品恰放入一個重量為m的背包可以獲得的最大價值 int c[][] = new int[n + 1][m + 1]; for (int i = 0; i < n + 1; i++) c[i][0] = 0; for (int j = 0; j < m + 1; j++) c[0][j] = 0; for (int i = 1; i < n + 1; i++) { for (int j = 1; j < m + 1; j++) { //當物品為i件重量為j時,如果第i件的重量(w[i-1])小於重量j時,c[i][j]為下列兩種情況之一: //(1)物品i不放入背包中,所以c[i][j]為c[i-1][j]的值 //(2)物品i放入背包中,則背包剩余重量為j-w[i-1],所以c[i][j]為c[i-1][j-w[i-1]]的值加上當前物品i的價值 if (w[i - 1] <= j) { if (c[i - 1][j] < (c[i - 1][j - w[i - 1]] + p[i - 1])) c[i][j] = c[i - 1][j - w[i - 1]] + p[i - 1]; else c[i][j] = c[i - 1][j]; } else c[i][j] = c[i - 1][j]; } } return c; }
運行結果為:
0 0 4 4 4 4 4 4 4 4
0 0 4 5 5 5 9 9 9 9
0 0 4 5 6 6 9 10 11 11
Process finished with exit code 0