完全背包问题
问题描述
完全背包是在N种物品中选取若干件(同一种物品可多次选取)放在空间为V的背包里
第i种物品的体积是v,价值是w
求将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大
—————————————————————————————
问题分析
首先看上面这张图
左边的黄色纵行代表的是第n件物品
上面的蓝灰色横行代表的是第n件物品的体积和价值
右边的绿色横行代表的是背包容量的大小
—————————————————————————————
分析
由上一篇0-1背包可知,当前状态只与上一次的状态有关,也就是只需要用一个一维数组(dp[])储存上一次的状态即可。
dp数组的含义是之前物品选择的最优解,之前的物品都有不选与多选的情况
所以对于第i件物品来说,也有不选与多选两种情况
角度一:
先从背包的角度来看,背包体积的选择范围是从0~V,但是当背包体积小于物品体积的时候,这个物品就是不选的,数据跟之前的相同。所以背包体积的选择范围是从v[i] ~ V。
在背包体积是j的情况下,物品i有选与不选两种情况,对应以下代码:
dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]);
这时候你有可能会问:不是物品还有多选的情况吗?为什么这里只有选与不选的情况呢?
因为咱是从前往后遍历的,也就是在背包体积为j的情况下,j之前的所有数据都是在这个物品选与不选中的最优解,所以到背包体积为j的之后,有可能之前这个物品已经选过多次了,或是一次都没选。这里的选与不选是针对一定体积背包来说的。
完整代码:
public class Main {
int N; //物品的数量为N
int V; //背包的容量为V
int[] dp = new int[Integer.MAX_VALUE];
public int completeBag1(int[] v, int[] w){
for(int i = 1; i <= N; i++){
for(int j = v[i]; j <= V; j++){
dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]);
}
}
return dp[V];
}
}
角度二:
咱再从物品的角度来说,每种物品都有不选与多选的情况
最外层循环确定物品是啥(i)
中间循环因为从前往后会破坏数据,所以从后往前,具体是如何破坏的,咱也在0-1背包中说过了。中间循环的目的是为了确定背包的体积(j)
最后一层循环是为了确定物品的个数,因为选的个数不能使个数 * 物品体积 > 背包体积,所以j - k * v[i] >= 0 (k)
所以dp[j]的值就应该在这k个物品放与不放中选择:
1.不放:就是dp[i]
2.放:这个背包得腾出k * v[i]的体积来放,同时价值增加了k * w[i]
dp[j] = Math.max(dp[j], dp[j - k * v[i]] + k * w[i]);
完整代码:
public class Main {
int N; //物品的数量为N
int V; //背包的容量为V
int[] dp = new int[Integer.MAX_VALUE];
//优化以后的代码
public int CompleteBag2(int[] v, int[] w){
for(int i = 1; i <= N; i++){
for(int j = V; j <= v[i]; j--){
for(int k = 0; j - k * v[i] >= 0; k++) {
dp[j] = Math.max(dp[j], dp[j - k * v[i]] + k * w[i]);
}
}
}
return dp[V];
}
}