一、问题
描述
给定 n 种物品和一个容量为 C 的背包,物品 i 的重量是 wi,其价值为 vi。
问:应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?
示例输入
12 6
4 6 2 2 5 1
8 10 6 3 7 2
示例输出
24
说明
包括三行,第一行分别是C和n,第二行是w,第三行是v。
即:
背包容量c=12
n=6
重量数组w = {4, 6, 2, 2, 5, 1}
价值数组v = {8, 10, 6, 3, 7, 2}
二、分析
01问题是典型的动态规划算法问题,动态规划算法与分治法一样,都是拆解成小问题,找出状态关系,然后用递推方法解决。为了能找出状态关系,往往需要自己定义出一个数列。在比较简单的动态规划中这个数列又往往是个二阶数组,并且二阶数组又是与算法中重要两个数据有关。本问题中最重要的两个量物品、背包重量。
定义:m[i][j]表示考虑到编号为i的物品、当背包容量为j时的最大可容纳价值。(其中i从0开始编号,考虑到i即从0-i的物品都考虑进去)。
递归方程:
(1)当w[i] > j的时候,说明放不下去,所以肯定不能放i号物品,所以:
m[i][j] = m[i-1][j]
(2)当w[i] <= j的时候,说明放的下去,那么问题就是放不放,如果放的话价值是m[i-1][j-w[i]] + v[i];如果不放下去,就是m[i][j] = m[i-1][j]。所以放不放显然取决于两个值的大小。
综上,得到递归方程:
if(j>=w[i])
m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
else
m[i][j]=m[i-1][j];
因为这里涉及m[i-1][j],所以递推需要把m[0][j]算出来,然后从1开始递推。m[0][j]比较简单,就是比较w[0]和j的关系。
三、代码
完整代码如下:
/**
*
* @param w 物品i的重量为wi
* @param v 物品i的价值为vi
* @param c 背包总的容量为c
* @return 能装入背包的总价值
*/
public static int getResult(int w[], int v[], int c) {
if(w.length != v.length) {
throw new IllegalArgumentException();
}
if (c <= 0) {
return 0;
}
int n = w.length;
//m[i][j] 表示 在背包容量剩余j的时候考虑到第i件物品的最大价值(i从0还是计数)
int m[][] = new int[n][c + 1];
for(int j = 0; j < c + 1; j++) {
if(w[0] > j) {
m[0][j] = 0;
} else {
m[0][j] = v[0];
}
}
for(int i = 1; i < n; i++) {
for(int j = 0; j < c + 1; j++) {
if(w[i] > j) {
m[i][j] = m[i - 1][j];
} else {
m[i][j] = Math.max(m[i - 1][j], m[i - 1][j - w[i]] + v[i]);
}
}
}
return m[n - 1][c];
}