揹包問題(Knapsack problem):給定一組物品,每種物品都有自己的重量和價格,在限定的總重量內,我們如何選擇,才能使得物品的總價格最高。即在總重量不超過W的前提下,總價值是否能達到V?
基本思路
這是最基礎的揹包問題,特點是:每種物品僅有一件,可以選擇放或不放。
用子問題定義狀態:即f[i][v]表示前i件物品恰放入一個容量爲v的揹包可以獲得的最大價值。則其狀態轉移方程便是:f[i][v]=max{ f[i-1][v], f[i-1][v-c[i]]+w[i] }。
這個方程非常重要,“將前i件物品放入容量爲v的揹包中”這個子問題,若只考慮第i件物品的策略(放或不放),那麼就可以轉化爲一個只牽扯前i-1件物品的問題。如果不放第i件物品,那麼問題就轉化爲“前i-1件物品放入容量爲v的揹包中”,價值爲f[i-1][v];如果放第i件物品,那麼問題就轉化爲“前i-1件物品放入剩下的容量爲v-c[i]的揹包中”,此時能獲得的最大價值就是f [i-1][v-c[i]]再加上通過放入第i件物品獲得的價值w[i]。
遞歸式:
動態規劃算法實現:
public class Knapsack { public static void knapsack(int[] v, int[] w, int c, int[][] m) { /** v[] w[] c 分別是價值、重量、和揹包容量數組 m[i][j]表示有i~n個物品,揹包容量爲j的最大價值。*/ int n = v.length-1; //n是下標,從0開始 int jMax = Math.min(w[n]-1, c); for(int j = 0; j <= jMax; j++) m[n][j] = 0; //當w[n]>j 有 m[n][j]=0 //m[n][j] 表示只有n物品,揹包的容量爲j時的最大價值 for (int l = w[n]; l <= c; l++) m[n][l] = v[n]; //當w[n]<=j 有m[n][j]=v[n] //遞歸調用求出m[][]其它值,直到求出m[0][c] for(int i = n-1; i >=1; i--) { jMax = Math.min(w[i]-1,c); for(int k = 0; k <=jMax; k++) m[i][k] = m[i+1][k]; for(int h = w[i]; h <= c; h++) m[i][h] = Math.max(m[i+1][h],m[i+1][h-w[i]]+v[i]); } m[0][c] = m[1][c]; if(c >= w[0]) m[0][c] = Math.max(m[0][c],m[1][c-w[0]]+v[0]); System.out.println("bestw ="+m[0][c]); } public static void traceback(int[][] m, int[] w, int c, int[] x) {// 根據最優值求出最優解(存在則爲1,不存在則爲0) //當x1=0時,由m[2][c]繼續構造最優解;若x1=1,則由m[2][c-w1]繼續構造最優解,依次類推,可構造出相應的最優解 int n = w.length-1; for(int i = 0; i<n;i++) if(m[i][c] == m[i+1][c]) x[i] = 0; //如果m[1][c]=m[2][c],則x1=0,否則x1=1 else{ x[i] = 1; c -= w[i]; } x[n] = (m[n][c]>0)?1:0; } public static void main(String[] args){ //測試 int[] ww = {2,2,6,5,4}; int[] vv = {6,3,5,4,6}; int[][] mm = new int[11][11]; knapsack(vv,ww,10,mm); int[] xx =new int[ww.length]; traceback(mm,ww,10,xx); System.out.print("0-1揹包最優解的序列爲:"); for(int i = 0;i<xx.length;i++) System.out.print(xx[i]+" "); } }
一個問題可以用動態規劃法求解的先決條件:
1、最優子結構性質:當問題的最優解包含了其子問題的最優解時,稱該問題具有最有子結構性質。
2、重疊子問題:每次產生的子問題並不總是新問題,有些子問題被反覆計算多次。
滿足了以上兩個條件的問題可以考慮用動態規劃法(將子問題的解記憶化存儲)求解,他是一種自底向上的遞歸算法。