- 0-1背包問題 :每個物品只有1件
- 完全背包問題:每個物品有無數件
- 多重背包問題:每個物品有不超過多少件的限制
- 混合背包問題:物品有的是1件,有的無數件,有的不超過多少件
1、0-1背包問題
題目描述:
有N件物品和一個容量是bagV的背包,每件物品只能使用一次。第 i件物品的體積是 v[i],價值是 w[i]。
求解將哪些物品裝入背包,可使這些物品的總體積不超過背包容量,且總價值最大。輸出最大價值。
思路:
對於每一個物品,有兩種結果:能裝下或者不能裝下。
- 如果不能裝下,這時的最大價值和前i-1個物品的最大價值是一樣的;
- 如果能裝下,裝了不一定大於當前相同體積的最優價值,所以要對裝該商品與不裝該商品得到的最大價值進行比較,取最大的那個。
設f[i][j]表示:背包容量為j時,前i個物品所能達到的最大價值。0<=j<=V
第i個商品體積為vi,價值為wi,則狀態轉移方程:
- j<vi, f[i][j] = f[i-1][j] //背包裝不下此物品,最大價值不變,還是為前i-1的最大價值
- j>=vi,f[i][j] = max{f[i-1][j],f[i-1][j-vi]+wi} // 背包裝得下,最大價值取裝與不裝該物品時同樣達到該體積的最大價值

/** * 利用二維數組 * @param N N個物品 * @param bagV 背包體積為bagV * @param v 物品體積(v[i]表示第i個物品體積,v[0]=0) * @param w 物品價值(w[i]表示第i個物品價值,w[0]=0) */ public static int bag0_1(int N, int bagV, int[] v, int[] w) { //f[i][j]表示背包容量為j時前i個商品的最大價值 int[][] f = new int[N+1][bagV+1]; for(int i = 1; i <= N; i++) { for(int j = 0; j <= bagV; j++) { if(j < v[i]) f[i][j] = f[i-1][j]; else f[i][j] = Math.max(f[i-1][j], f[i-1][j-v[i]]+w[i]); } } return f[N][bagV]; }
用一維數組的話,設f[j]表示背包容量為j時的最大價值,狀態轉移方程:f[j] = max[f[j],f[j-vi]+wi}
/** * 利用一維數組實現 * @param N N個物品 * @param bagV 背包體積為bagV * @param v 物品體積(v[i]表示第i個物品體積,v[0]=0) * @param w 物品價值(w[i]表示第i個物品價值,w[0]=0) */ public static int bag0_1(int N, int bagV, int[] v, int[] w) { //f[j]表示背包體積為j時最大價值 int[] f = new int[bagV + 1]; for(int i = 1; i <= N; i++) { for(int j = bagV; j >= v[i]; j--) f[j] = Math.max(f[j], f[j-v[i]]+w[i]); } return f[bagV]; }
注:如果要求恰好裝滿背包:
求法相同,不過初始化是除了f[1]初始化為0(背包容量為1時的最大價值為0),其他都初始化為負無窮
/** * 在恰好裝滿背包的情況下最多獲取多少價值? * 初始化時,除了f[i][1]為0外(第一列),其他全為負無窮 */ public static int fullBag0_1(int N, int bagV, int[] v, int[] w) { int[] f = new int[bagV+1]; //除了f[i][1]其他都為負無窮 for(int j = 2; j < bagV; j++) { f[j] = Integer.MIN_VALUE;//其他全為負無窮 } for(int i = 1; i <= N; i++) { for (int j = bagV; j >= v[i]; j--) f[j] = Math.max(f[j], f[j - v[i]] + w[i]); } int res = f[bagV]; if(res < 0) res = -1; return res; }
2、完全背包問題
題目描述:
有 N 種物品和一個容量是 bagV 的背包,每種物品都有無限件可用。第 i 種物品的體積是 vi,價值是 wi。
求解將哪些物品裝入背包,可使這些物品的總體積不超過背包容量,且總價值最大。輸出最大價值。
思路:
設f[i][j]表示背包容量為j時,前i個物品所能達到的最大價值。0<=j<=bagV
第i個商品體積為vi,價值為wi,則狀態轉移方程:
- j<vi,f[i][j] = f[i-1][j] //背包裝不下此物品,最大價值不變,還是為前i-1的最大價值
- j>=vi,f[i][j] = max{f[i-1][j],f[i-1][j-k*vi]+k*wi} //背包裝得下,最大價值取裝與不裝該物品時同樣達到該體積的最大價值,與0-1不同的是,可以裝k個。

/** * 利用二維數組實現 * @param N N個物品 * @param bagV 背包體積為bagV * @param v 物品體積(v[i]表示第i個物品體積,v[0]=0) * @param w 物品價值(w[i]表示第i個物品價值,w[0]=0) */ public static int competeBag(int N, int bagV, int[] v, int[] w) { //f[i][j]表示背包容量為j時前i個商品的最大價值 int[][] f = new int[N+1][bagV+1]; for(int i = 1; i <= N; i++) { for(int j = 0; j <= bagV; j++) { if(j < v[i]){ f[i][j] = f[i-1][j]; }else{ for(int k = 1; k*v[i] <= j; k++) f[i][j] = Math.max(f[i-1][j], f[i-1][j-k*v[i]]+k*w[i]); } } } return f[N][bagV]; }
利用一維數組的話,設f[j]表示背包容量為j時的最大價值,狀態轉移方程:f[j] = max[f[j],f[j-k*vi]+k*wi}

/** * 利用一維數組實現 * @param N N個物品 * @param bagV 背包體積為bagV * @param v 物品體積(v[i]表示第i個物品體積,v[0]=0) * @param w 物品價值(w[i]表示第i個物品價值,w[0]=0) */ public static int competeBag(int N, int bagV, int[] v, int[] w) { //f[j]表示背包容量為j時商品的最大價值 int[] f = new int[bagV+1]; for(int i = 1; i <= N; i++) { for(int j = 0; j <= bagV; j++) { for(int k = 1; k * v[i] <= j; k++) { f[j] = Math.max(f[j], f[j-k*v[i]]+k*w[i]); } } } return f[bagV]; }
優化代碼:與0-1背包不同的是第二層循環j從小到大順序遍歷(0-1背包是從大到小逆序遍歷):
/** * 利用一維數組實現 * @param N N個物品 * @param bagV 背包體積為bagV * @param v 物品體積(v[i]表示第i個物品體積,v[0]=0) * @param w 物品價值(w[i]表示第i個物品價值,w[0]=0) */ public static int competeBag(int N, int bagV, int[] v, int[] w) { //f[j]表示背包容量為j時商品的最大價值 int[] f = new int[bagV+1]; for(int i = 1; i <= N; i++) { for(int j = v[i]; j <= bagV; j++) f[j] = Math.max(f[j], f[j-v[i]]+w[i]); } return f[bagV]; }
3、多重背包問題
題目描述:
有 N 種物品和一個容量是 bagV 的背包。第 i 種物品最多有 si 件,每件體積是 vi,價值是 wi。
求解將哪些物品裝入背包,可使物品體積總和不超過背包容量,且價值總和最大。輸出最大價值。
思路:
和完全背包類似,不同的是第二層循環j時多了一個對物品個數的限制。
/** * 利用一維數組實現 * @param N N個物品 * @param bagV 背包體積為bagV * @param v 物品體積(v[i]表示第i個物品體積,v[0]=0) * @param w 物品價值(w[i]表示第i個物品價值,w[0]=0) * @param s s[i]表示第i個物品最多有多少個 ,s[0]=0 */ public static int multipleBag(int N, int bagV, int[] v, int[] w, int[] s) { //f[j]表示背包容量為j時商品的最大價值 int[] f = new int[bagV+1]; for(int i = 1; i <= N; i++) { for(int j = 0; j <= bagV; j++) { for(int k = 1; k <= s[i] && k * v[i] <= j; k++) { f[j] = Math.max(f[j], f[j-k*v[i]]+k*w[i]); } } } return f[bagV]; }
利用二進制優化,轉化為0-1背包問題:
一個數a,我們可以按照二進制來分解為 a=1+2+4+8……+2^n+剩下的數,我們把a拆成這么多項,可以證明,這么多項可以組合出1~a的每一個數。
不管最優策略選擇幾件第i種物品,總可以表示成若干件物品的和。利用二進制拆分將a拆成若干數字的和,假設拆成M個數字,則這樣把原問題轉化為物品數量為M的0-1背包問題
//先定義一個類來存放新商品 class Goods { int v; //體積 int w; //價值 public Goods(int v, int w) { this.v = v; this.w = w; } } ----------------------------------------------- /** * 二進制優化,轉為0-1背包問題來實現 * @param N N個物品 * @param bagV 背包體積為bagV * @param v 物品體積(v[i]表示第i個物品體積,v[0]=0) * @param w 物品價值(w[i]表示第i個物品價值,w[0]=0) * @param s s[i]表示第i個物品最多有多少個 ,s[0]=0 */ public static int multipleBag(int N, int bagV, int[] v, int[] w, int[] s) { //存放新商品的體積、價值 ArrayList<Goods> list = new ArrayList<Goods>(); //s[i]拆為一些數的和,重新存放商品,二進制轉換為0-1背包問題 for(int i = 1; i <= N; i++) { int ss = s[i]; for(int k = 1; k <= ss; k *= 2) { ss -= k; list.add(new Goods(k*v[i], k*w[i])); } //剩下的數 if(ss > 0) list.add(new Goods(ss*v[i], ss*w[i])); } //按照0-1背包問題求解 int[] f = new int[bagV+1]; for(Goods good : list) { for(int j = bagV; j >= good.v; j--) { f[j] = Math.max(f[j], f[j-good.v]+good.w); } } return f[bagV]; }
4、混合背包問題
有 N種物品和一個容量是 bagV的背包。物品一共有三類:
- 第一類物品只能用1次(01背包);
- 第二類物品可以用無限次(完全背包);
- 第三類物品最多只能用 si 次(多重背包);
每種體積是 vi,價值是 wi。
求解將哪些物品裝入背包,可使物品體積總和不超過背包容量,且價值總和最大。輸出最大價值。
這里我們給出輸入輸出格式:
輸入格式:
第一行兩個整數,N,V,用空格隔開,分別表示物品種數和背包容積。
接下來有 N行,每行三個整數 vi,wi,si,用空格隔開,分別表示第 i種物品的體積、價值和數量。
si=−1 表示第 i種物品只能用1次;
si=0 表示第 i種物品可以用無限次;
si>0 表示第 i種物品可以使用 si 次;
輸出格式:
輸出一個整數,表示最大價值。輸入樣例:
4 5
1 2 -1
2 4 1
3 4 0
4 5 2
輸出樣例:8
思路:
將多重背包轉換為0-1背包進行處理,所以最后只需要處理兩種背包:0-1背包與完全背包。
//定義一個Goods類 class Goods { int v; //體積 int w; //價值 int s; //物品類型:-1、0、>0 public Goods(int v, int w, int s) { this.v = v; this.w = w; this.s = s; } } public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int N = sc.nextInt(); //N個物品 int bagV = sc.nextInt(); //背包容積為bagV //存儲物品的體積和價值 int[] v = new int[N+1]; //體積 int[] w = new int[N+1]; //價值 int[] s = new int[N+1]; //物品類型:-1--只有1件、0--有無數件、>0--有這些件 for(int i = 1; i <= N; i++) { v[i] = sc.nextInt(); w[i] = sc.nextInt(); s[i] = sc.nextInt(); } System.out.println(mixtureBag(N, bagV, v, w, s)); } /* * s[i]=-1: 0-1背包 * s[i]=0 :完全背包 * s[i]>0 :多重背包 * 多重背包可以轉換為0-1背包進行處理 */ public static int mixtureBag(int N, int bagV, int[] v, int[] w, int[] s) { //存放商品的 體積、價值、類型 ArrayList<Goods> list = new ArrayList<Goods2>(); for(int i = 1; i <= N; i++){ if(s[i] == -1 || s[i] == 0) list.add(new Goods(v[i], w[i], s[i])); else { //多重背包二進制優化轉為0-1背包問題 int ss = s[i]; for(int k = 1; k <= s[i]; k *= 2) { ss -= k; list.add(new Goods(k*v[i], k*w[i], -1)); } if(ss > 0) list.add(new Goods(ss*v[i], ss*w[i], -1)); } } int[] f = new int[bagV+1]; for(Goods good : list) { //0-1背包 if(good.s == -1){ for(int j = bagV; j >= good.v; j--) f[j] = Math.max(f[j], f[j-good.v]+good.w); } //完全背包 else{ for(int j = good.v; j <= bagV; j++) f[j] = Math.max(f[j], f[j-good.v]+good.w); } } return f[bagV]; } }
5、二維費用的背包問題
題目描述:
有 N 件物品和一個容量是 V 的背包,背包能承受的最大重量是 M。每件物品只能用一次。體積是 vi,重量是 mi,價值是 wi。
求解將哪些物品裝入背包,可使物品總體積不超過背包容量,總重量不超過背包可承受的最大重量,且價值總和最大。輸出最大價值。
輸入格式:
第一行兩個整數,N,V,M,用空格隔開,分別表示物品件數、背包容積和背包可承受的最大重量。
接下來有 N 行,每行三個整數 vi,mi,wi,用空格隔開,分別表示第 i 件物品的體積、重量和價值。
輸出格式
輸出一個整數,表示最大價值。
輸入樣例
4 5 6 1 2 3 2 4 4 3 4 5 4 5 6
輸出樣例:
8
public static void main(String[] args) { Scanner sc = new Scanner(System.in); int N = sc.nextInt(); //物品數 int bagV = sc.nextInt(); //背包體積 int bagM = sc.nextInt(); //背包重量 int[] v = new int[N+1]; int[] m = new int[N+1]; int[] w = new int[N+1]; for(int i = 1; i <= N; i++) { v[i] = sc.nextInt(); m[i] = sc.nextInt(); w[i] = sc.nextInt(); } System.out.println(bag2D(N, bagV, bagM, v, m, w)); }
public static int bag2D(int N, int bagV, int bagM, int[] v, int[] m, int[] w) { //f[i][j]表示背包容量為i、重量為j時的最大價值 int[][] f = new int[bagV+1][bagM+1]; for(int k = 1; k <= N; k++) //第k個物品 //都要倒序 for(int i = bagV; i >= v[k]; i--) for(int j = bagM; j >= m[k]; j--) f[i][j] = Math.max(f[i][j], f[i-v[k]][j-m[k]]+w[k]); return f[bagV][bagM]; }
6、分組背包問題
題目描述:
有 N 組物品和一個容量是 V 的背包。每組物品有若干個,同一組內的物品最多只能選一個。每件物品的體積是 vij,價值是 wij,其中 i是組號,j 是組內編號。
求解將哪些物品裝入背包,可使物品總體積不超過背包容量,且總價值最大。輸出最大價值。
輸入格式
第一行有兩個整數 N,VN,V,用空格隔開,分別表示物品組數和背包容量。
接下來有 NN 組數據:
- 每組數據第一行有一個整數 SiSi,表示第 ii 個物品組的物品數量;
- 每組數據接下來有 SiSi 行,每行有兩個整數 vij,wijvij,wij,用空格隔開,分別表示第 ii 個物品組的第 jj 個物品的體積和價值;
輸出格式
輸出一個整數,表示最大價值。
輸入樣例
3 5 2 1 2 2 4 1 3 4 1 4 5
輸出樣例:
8
public static void main(String[] args) { Scanner sc = new Scanner(System.in); //N組物品 int N = sc.nextInt(); //背包體積 int bagV = sc.nextInt(); //s[i]表示第i組有多少件商品 int[] s = new int[N+1]; //v[i][j]表示第i組第j件商品的體積 int[][] v = new int[N+1][]; //w[i][j]表示第i組第j件商品的價值 int[][] w = new int[N+1][]; for(int i = 1; i <= N; i++) {//第i組商品 s[i] = sc.nextInt(); v[i] = new int[s[i]+1]; w[i] = new int[s[i]+1]; for(int j = 1; j <= s[i]; j++) { v[i][j] = sc.nextInt(); w[i][j] = sc.nextInt(); } } System.out.println(groupBag(N, bagV, s, v, w)); }
//一組商品只能選一個,相當於0-1背包問題 public static int groupBag(int N, int bagV, int[] s, int[][] v, int[][] w) { //f[j]表示背包容量為j時的最大價值 int[] f = new int[bagV+1]; for(int i = 1; i <= N; i++){ for(int j = bagV; j >=0; j--) { //選第i組物品的第k件商品 for(int k = 1; k <= s[i]; k++) { if(j >= v[i][k]) f[j] = Math.max(f[j], f[j-v[i][k]]+w[i][k]); } } } return f[bagV]; }
7、背包問題求方案數
問題描述:
有 N件物品和一個容量是 V的背包。每件物品只能使用一次。第 i 件物品的體積是 vi,價值是 wi。
求解將哪些物品裝入背包,可使這些物品的總體積不超過背包容量,且總價值最大。輸出最優選法的方案數。注意答案可能很大,請輸出答案模 109+7 的結果。
輸入格式:
第一行兩個整數,N,V,用空格隔開,分別表示物品數量和背包容積。
接下來有 N行,每行兩個整數 vi,wi,用空格隔開,分別表示第 i件物品的體積和價值。輸出格式:
輸出一個整數,表示 方案數 模 109+7 的結果。輸入樣例:
4 5
1 2
2 4
3 4
4 6
輸出樣例:2
public static void main(String[] args) { Scanner sc = new Scanner(System.in); int N = sc.nextInt(); //N個物品 int bagV = sc.nextInt(); //背包容積為bagV //存儲物品的體積和價值 int[] v = new int[N+1]; //體積 int[] w = new int[N+1]; //價值 for(int i = 1; i <= N; i++) { v[i] = sc.nextInt(); w[i] = sc.nextInt(); } System.out.println(numsOfBag(N, bagV, v, w)); } /**最優方案總數,指物品總價值最大的方案數。*/ public static int numsOfBag(int N, int bagV, int[] v, int[] w) { //f[j]表示背包容量為j時的最大價值 int[] f = new int[bagV+1]; //num[j]表示背包容量為j時的最大方案數 int[] num = new int[bagV+1]; Arrays.fill(num, 1);//f[]都初始化為1 final int mod = 10000007; for(int i = 1; i <= N; i++) { for(int j = bagV; j >= v[i]; j--) { //如果加入該商品價值更大,必然加入該商品,因為是必然,所以這樣的方案數量不變 if(f[j] < f[j-v[i]]+w[i]){ num[j] = num[j-v[i]]; num[j] %= mod; } //如果加入該商品與不加的價值相等,那么加或不加都可以,都是一種方案,所以方案數相加 else if(f[j] == f[j-v[i]]+w[i]){ num[j] += num[j-v[i]]; num[j] %= mod; } //如果加入該商品價值更小,那么肯定不加,方案數保持num[i-1][j]即可 //統一更新f[j] f[j] = Math.max(f[j], f[j-v[i]]+w[i]); } } return num[bagV]; }
8、背包問題求具體方案
題目描述:
有 N件物品和一個容量是 V 的背包。每件物品只能使用一次。第 i 件物品的體積是 vi,價值是 wi。
求解將哪些物品裝入背包,可使這些物品的總體積不超過背包容量,且總價值最大。輸出字典序最小的方案。這里的字典序是指:所選物品的編號所構成的序列。物品的編號范圍是 1…N。輸入格式:
第一行兩個整數,N,V,用空格隔開,分別表示物品數量和背包容積。
接下來有 N行,每行兩個整數 vi,wi,用空格隔開,分別表示第 i件物品的體積和價值。
輸出格式:
輸出一行,包含若干個用空格隔開的整數,表示最優解中所選物品的編號序列,且該編號序列的字典序最小。物品編號范圍是 1…N。輸入樣例:
4 5
1 2
2 4
3 4
4 6
輸出樣例:1 4
public static void main(String[] args) { Scanner sc = new Scanner(System.in); int N = sc.nextInt(); //N個物品 int bagV = sc.nextInt(); //背包容積為bagV //存儲物品的體積和價值 int[] v = new int[N+1]; //體積 int[] w = new int[N+1]; //價值 ///////因為要求字典序最小,我們把輸入逆序,然后提供一個記錄物品序號的數組index int[] index = new int[N+1]; int indexTemp = 0; ////輸入逆序 for(int i = N; i >= 1; i--) { v[i] = sc.nextInt(); w[i] = sc.nextInt(); ///////// index[i] = ++indexTemp; } List<Integer> list = progectOfBag(N, bagV, v, w,index); for(Integer item : list) System.out.print(item + " "); } public static List<Integer> progectOfBag(int N, int bagV, int[] v, int[] w, int[] index) { //首先按0-1背包問題求最大價值,這里二維實現,f[i][j]表示背包容量為j時前i-1個物品的最大價值 int[][] f = new int[N+1][bagV+1]; for(int i = 1; i <= N; i++) { for(int j = 0; j <= bagV; j++) { if(j >= v[i]) f[i][j] = Math.max(f[i-1][j], f[i-1][j-v[i]]+w[i]); else f[i][j] = f[i-1][j]; } } //存方案 List<Integer> list = new ArrayList<Integer>(); //反推方案 int vol = bagV; //從最后一個往前推 for(int i = N; i >= 1; i--) { if(vol >= v[i] && f[i][vol] == f[i-1][vol-v[i]]+w[i]){ //說明選擇了當前物品 list.add(index[i]); vol -= v[i]; } if(vol <= 0) break; } return list; }
9、一個類似背包問題的問題,求無價值的所有方案
題目描述:
假設有一個能裝入總體積為bagV的背包和 N件體積分別為v1,v2 , … , vn的物品,能否從N件物品中挑選若干件恰好裝滿背包,即使v1+v2+…+vn=bagV,要求找出所有滿足上述條件的解。
輸入格式:
第一行兩個整數,N,bagV,用空格隔開,分別表示物品數量和背包容積。
接下來一行有N個整數,用空格隔開,分別表示第 i件物品的體積vi。
輸出格式:
輸出具體方案輸入示例:
6 10
1 8 4 3 5 2
輸出:
1 4 3 2
1 4 5
8 2
3 5 2
注意:這個做法只適合N<32的情況,因為1<<32對int型變量會溢出。 1L<<x的話x的最大值為63
public static void main(String[] args) { Scanner sc = new Scanner(System.in); //物品個數 int N = sc.nextInt(); //背包體積 int bagV = sc.nextInt(); //物品體積 int[] v = new int[N]; for(int i = 0; i < N; i++) v[i] = sc.nextInt(); fill(v, N, bagV); } /* 用1到2^N的二進制來求解,若二進制數該位置是1,則將其取出求和: 1表示成選取狀態, 0表示成未選取狀態。 標記中有幾個 1就是代表選取了幾個數,然后再去遍歷這些 1所有可能存在的排列方式,最后做一個判斷,這個判斷就是: 每一種排列方式,都代表着數組中不同位置的被選中的數的組合,所以這里就是將選中的這些數字進行求和運算,然后判斷求出的和是不是等於bagV 。 */ public static void fill(int[] v, int N, int bagV) { //從1循環到2^N,相當於對v從00...01一直循環到11...11 for(int i = 1; i <= 1 << N; i++){ int sum = 0; String temp = ""; for(int j = 0; j < N; j++) { //用i與2^j進行位與運算,若結果不為0,則表示第j位不為0,從數組中取出第j個數 if((i & 1 << j) != 0) { sum += v[j]; temp += v[j] + " "; } } if(sum == bagV) System.out.println(temp); } }
參考:dd大牛的《背包九講》