有n个物品,每个都具有价值(记作v)和重量(记作w)两个属性。现有一个背包,最多只能装总重量为W的物品。要从这n个物品中选择若干个放入背包,使得背包中所有物品的总价值最大。(这里假设每个物品的重量w都是整数)。
此问题可以用动态规划求解。设n个物品的价值分别是v1,v2,……vn,重量分别是w1,w2,……,wn。
考虑如下的问题:从n个物品中的前k个物品(下标分别是1,2,……k)中选取若干个放在容积为W的背包中,使得背包中所有物品的总价值最大。将此最大价值记为C(W, k)。
最优子结构如下:从C(W, k)的最优方案中去掉任意一个物品(假设此物品的下标为i,重量为wi,)。那么剩下的物品刚好是从1,2,……,i-1,i+1,……k等k-1个物品中选取若干个放在容积为W-wi的背包中,使得背包中所有物品的总价值最大的方案。(使用cut and paste的方式易证)
递推式如下:
若C(W, k)的最优方案中包含了第k个物品,那么C(W, k) = C(W-wk, k - 1) + vk。注意,这里用到了刚刚提到的最优子结构。
若C(W, k)的方案中不包含第k个物品,那么C(W, k) = C(W, k - 1)
所以,C(W, k)为上述两种情况中的较大者:
C(W, k) = max(C(W-wk, k - 1) + vk, C(W, k - 1))
容易看出,重叠子问题有很多。
下面举个例子。背包最大容量是12,一共有6个物品,物品的价值和重量见下表:
编号 |
1 |
2 |
3 |
4 |
5 |
6 |
价值 |
33 |
34 |
37 |
32 |
31 |
39 |
重量 |
5 |
7 |
7 |
1 |
8 |
4 |
用一个辅助表来计算C(W, k)。
下表中,65(1,4)表示选取1号和4号物品,总价值是65。
|
物品k:1 |
2 |
3 |
4 |
5 |
6 |
容量w:0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
32(4) |
32(4) |
32(4) |
2 |
0 |
0 |
0 |
32(4) |
32(4) |
32(4) |
3 |
0 |
0 |
0 |
32(4) |
32(4) |
32(4) |
4 |
0 |
0 |
0 |
32(4) |
32(4) |
39(6) |
5 |
33(1) |
33(1) |
33(1) |
33(1) |
33(1) |
39(6) |
6 |
33(1) |
33(1) |
33(1) |
65(1,4) |
65(1,4) |
65(1,4) |
7 |
33(1) |
34(2) |
37(3) |
65(1,4) |
65(1,4) |
65(1,4) |
8 |
33(1) |
34(2) |
37(3) |
69(3,4) |
69(3,4) |
69(3,4) |
9 |
33(1) |
34(2) |
37(3) |
69(3,4) |
69(3,4) |
72(1,6) |
10 |
33(1) |
34(2) |
37(3) |
69(3,4) |
69(3,4) |
104(1,4,6) |
11 |
33(1) |
34(2) |
37(3) |
69(3,4) |
69(3,4) |
104(1,4,6) |
12 |
33(1) |
67(1,2) |
70(1,3) |
70(1,3) |
70(1,3) |
108(3,4,6) |
代码如下:
1 import java.util.HashSet; 2 import java.util.Set; 3 4 public class Main { 5 6 private static int W = 12; 7 8 private static class Solution { 9 public Solution() { 10 value = -1; 11 items = new HashSet(); 12 } 13 14 private int value; 15 private Set items; 16 17 public void setValue(int value) { 18 this.value = value; 19 } 20 21 public int getValue() { 22 return value; 23 } 24 25 public void addToItem(Integer n) { 26 items.add(n); 27 } 28 public void addToItem(Set set) { 29 items.addAll(set); 30 } 31 32 public Set getItems() { 33 return items; 34 } 35 }; 36 37 private static int getValue(int index) { 38 int[] value = {33, 34, 37, 32, 31, 39}; 39 40 return value[index - 1]; 41 } 42 43 private static int getWeight(int index) { 44 int[] weight = {5, 7, 7, 1, 8, 4}; 45 46 return weight[index - 1]; 47 } 48 49 private static void printSolution(Solution[][] arraySolution) { 50 for (int i = 0; i < arraySolution.length; ++i) { 51 String s = ""; 52 for (int j = 0; j < arraySolution[i].length; ++j) { 53 s += String.valueOf(arraySolution[i][j].getValue()) + arraySolution[i][j].getItems() + "\t"; 54 } 55 56 System.out.println(s); 57 } 58 } 59 60 private static void calc(Solution[][] arraySolution, int i, int j) { 61 arraySolution[i][j] = new Solution(); 62 if ((0 == i) || (0 == j)) 63 { 64 arraySolution[i][j].setValue(0); 65 } else { 66 int nValue; 67 int nWithoutCurrItem = arraySolution[i][j - 1].getValue(); 68 69 int nCurrWeight = getWeight(j); 70 if (nCurrWeight > i) { 71 nValue = nWithoutCurrItem; 72 arraySolution[i][j].addToItem(arraySolution[i][j - 1].getItems()); 73 } else { 74 int nCurrValue = getValue(j); 75 int nWithCurrItem = arraySolution[i - nCurrWeight][j - 1].getValue() + nCurrValue; 76 77 if (nWithoutCurrItem > nWithCurrItem) { 78 nValue = nWithoutCurrItem; 79 arraySolution[i][j].addToItem(arraySolution[i][j - 1].getItems()); 80 } else { 81 nValue = nWithCurrItem; 82 arraySolution[i][j].addToItem(arraySolution[i - nCurrWeight][j - 1].getItems()); 83 arraySolution[i][j].addToItem(j); 84 } 85 } 86 87 arraySolution[i][j].setValue(nValue); 88 } 89 } 90 91 private static void calc(Solution[][] arraySolution) { 92 for (int i = 0; i < arraySolution.length; ++i) { 93 for (int j = 0; j < arraySolution[i].length; ++j) { 94 calc(arraySolution, i, j); 95 } 96 } 97 } 98 99 public static void main(String[] args) { 100 Solution arraySolution[][] = new Solution[W + 1][7]; 101 102 calc(arraySolution); 103 104 printSolution(arraySolution); 105 } 106 107 }