完全背包问题描述
- 有C种物品和一个容量为V的背包,每种物品都有无限件。第i件物品的体积是v[i],价值是w[i].求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
与之前的01背包不同的是,每个物品都有无限个,对于同一个物品,可以放置多个。
基本解法
首先还是先来用基本解法来一遍,不带优化的。先看多个物品的状态转移方程。
- 当不选择该物品时候,这个时候值跟01背包的一样,还是dp[i - 1][fv];
- 当选择该物品时候,对于一个固定容量,如果该物品能被放进去,要想价值最大,那就能放几个放几个,这时候的最大个数是
N = fv / w[i],即当前背包容量除以当前物品重量;得到最多放几个之后,就能计算出放入物品的总重量和最大价值了dp[i - 1][fv - N * w[i]] + N * v[i];
/**
* @param V 最大容量
* @param C 物品个数
* @param v 价值数组
* @param w 重量数组
* @return
*/
public int getMaxValue1(int V, int C, int[] v, int[] w) {
int[][] dp = new int[C][V + 1]; //[物品][重量] -> 最大价值
//初始化起始数据
for (int fv = 0; fv <= V; fv++) {
//初始化只有一件物品时候,使劲往里面放吧,能放几件放几件
if (w[0] <= fv) {
int N = fv / w[0];
dp[0][fv] = N * v[0];
}
}
for (int i = 1; i < C; i++) {
for (int fv = 0; fv <= V; fv++) {
//假设放入N个当前物品
//状态转移方程 : dp[i][fv] = max( dp[i-1][fv] , dp[i-1][ fv- N*w[i] ] + N*v[i] )
if (fv - w[i] >= 0) {
int N = fv / w[i];
//该物品大于剩余容量,可以放入背包,此时比较放入该物品与不放该物品的值
dp[i][fv] = Math.max(dp[i - 1][fv], dp[i - 1][fv - N * w[i]] + N * v[i]);
} else {
//改物品不能放入背包,直接取不放该物品的值
dp[i][fv] = dp[i - 1][fv];
}
}
}
return dp[C - 1][V];
}
@Test
public void testgetMaxValue1() {
int V = 4;
int C = 3;
int[] v = {15, 20, 30};
int[] w = {1, 3, 4};
System.out.println(JSONObject.toJSONString(getMaxValue1(V, C, v, w))); //60
}
空间优化
先来看之前01背包使用滚动数组优化后的代码:
public int getMaxValueUsingArray2(int V, int C, int[] v, int[] w) {
int[] dp = new int[V + 1]; //[重量],表示此重量时候能获得的最大价值
//初始化起始数据
Arrays.fill(dp, 0);
for (int i = 0; i < C; i++) { //物品
for (int fv = V; fv >= w[i]; fv--) { //重量
//从后往前遍历,物品不会被重复放置
dp[fv] = Math.max(dp[fv], dp[fv - w[i]] + v[i]);
}
}
return dp[V];
}
当时在内层遍历时候,对于01背包问题,因为物品都只有一个,所以必须从后往前遍历,但是对于我们的完全背包问题,我们从0往后遍历,则刚好符合物品可以放置多个。 下面是经过优化后的代码:
/**
* @param V 最大容量
* @param C 物品个数
* @param v 价值数组
* @param w 重量数组
* @return
*/
public int getMaxValue2(int V, int C, int[] v, int[] w) {
int[] dp = new int[V + 1]; //[重量] -> 最大价值
for (int i = 0; i < C; i++) {
for (int fv = 0; fv <= V; fv++) {
if (fv - w[i] >= 0) {
dp[fv] = Math.max(dp[fv], dp[fv - w[i]] + v[i]);
}
}
}
return dp[V];
}