目录
问题:
给定3个物品
a 价值1000, 重量1kg
b 价值2000, 重量4kg
c 价值1500, 重量3kg
用容量为4kg的背包最多可以装价值多少的物品?
背包问题就是类似这种给定容量求最优解的问题,有很多种,这里说的是01背包问题。
01背包:所有物品只有一个,只所以背包中任意物品的的数量只可能是0 或者 1。
动态规划思路:
当前情况的思考建立在之前的思考之上。
01背包的逻辑思考过程: - -之前网上各种版本都是来个表格然后就开始代码了。。。一脸懵逼
我先假设只考虑第1个物品a,这样我就可以得到:
背包最大容量为0, 1,2,3,4时,最大价值为0, 1000,1000,1000,1000
然后再考虑前2个物品(a 和 b)
先考虑:
背包最大容量为0,1,2,3时, b装不下,所以还是用只考虑前1个物品时的策略,可以得到0, 1000,1000,1000
背包最大容量为4时,b可以装下了,这时候就面临选择,比较:是考虑往背包中加入b时的价值高, 还是只考虑前1个物品时的价值高。
1. 假设加入了b: 背包剩余可用的容量是: 背包最大容量 - b占用的容量 = 4 - 4 = 0; 此时背包物品的最大价值是:b的价值 + 剩余容量所能存放的还没加入b之前的最大值。
而剩余容量所能存放的还没加入b之前的最大值,也就是只考虑a的情况下的值,我们在之前那一行中已经列出来的背包容量为所有情况下的最大价值中已经全部列出来了,此时可以利用上一行得到。
此时如果加入了b,那么背包最多还有0kg空间可以用
而上面已经得到: 背包最大容量为0, 1,2,3,4时,最大价值为0, 1000,1000,1000,1000 , 背包空间为0时,最大价值为0.
所以此时可以得到,如果把b加入背包,那么 最大价值是 b的价值 + 背包容量为0时,只考虑b之前的物品时,背包能存放的最大价值 = 2000 + 0;
2. 假设不把b放入背包, 背包的最大价值 等于 只考虑b之前物品时的最大价值,也就是 背包最大容量所能存放的还没加入b之前的最大值。
比较1 和 2 两种策略谁的值大,那么谁就是考虑目前所有物品时的最大价值。 也就是2000
此时背包最大容量为0,1,2,3,4时,对应最大值为 0,1000,1000,1000,2000
然后c和b的考虑模式一样
先试试加入c,此时背包除去c的占用容量后可用容量为4 -3 = 1, 此时背包物品的最大价值是:c的价值 + 剩余容量所能存放的还没加入c之前的最大值。 根据上一步得到背包最大容量为1时,最大价值为1000
所以加入c最大价值为 1500 + 1000 = 2500;
不加入c时,最大价值为上一步考虑b之后,背包容量为 4 时的最大价值: 2000;
比较后可以得到,加入c时的价值更高,得到2500;
。。。
如果还有别的物品,依次类推直到所有物品都考虑完毕。
package dynamic;
public class bag {
//01背包问题
public static void main(String[] args){
int[] v = {0, 1000, 2000, 1500, 2000}; //物品价格,第0个物品价格为0, 单纯为了逻辑清楚,这样index = 物品编号
int[] w = {0, 1, 4, 3, 2}; //物品需要的容量,第0个物品重量为0
int capacity = 4; // 背包最大可以使用的容量
System.out.println("最多可在背包中存放价值为:" + maxValue(v, w, capacity) + "的物品");
}
public static int maxValue(int[] v, int[] w, int capacity){
int[][] value = new int[v.length][capacity + 1]; // value[i][j]表示只考虑前i个物品,在能用j 容量的情况下最多可以放价值为value[i][j]的东西
for(int i = 1; i < v.length; i++){ //从第1个物品开始,依次推导
for(int j = 1; j <= capacity; j++) { //从最大容量是1到背包最大值,依次推导
if (w[i] > j) {
value[i][j] = value[i - 1][j]; //如果新加入考虑的物品的重量已经超过了背包最大容量,则不用考虑,依然使用只考虑它之前那些物品的方案
} else {
value[i][j] = Math.max(value[i - 1][j], v[i] + value[i - 1][j - w[i]]);
}
}
}
for(int i = 0; i < value.length; i++){
for(int j =0; j < value[i].length; j++){
System.out.print(value[i][j] + " ");
}
System.out.println();
}
return value[v.length - 1][capacity];
}
}
得到哪些物品被放入了
package dynamic;
public class bag {
//01背包问题
public static void main(String[] args){
int[] v = {0, 1000, 2000, 1500}; //物品价格,第0个物品价格为0, 单纯为了逻辑清楚,这样index = 物品编号
int[] w = {0, 1, 4, 3}; //物品需要的容量,第0个物品重量为0
int capacity = 4; // 背包最大可以使用的容量
System.out.println("最多可在背包中存放价值为:" + maxValue(v, w, capacity) + "的物品");
}
public static int maxValue(int[] v, int[] w, int capacity){
int[][] value = new int[v.length][capacity + 1]; // value[i][j]表示只考虑前i个物品,在能用j 容量的情况下最多可以放价值为value[i][j]的东西
int[][] g = new int[v.length][capacity + 1]; //用来记录存放了哪些物品
for(int i = 1; i < v.length; i++){ //从第1个物品开始,依次推导
for(int j = 1; j <= capacity; j++) { //从最大容量是1到背包最大值,依次推导
if (w[i] > j) {
value[i][j] = value[i - 1][j]; //如果新加入考虑的物品的重量已经超过了背包最大容量,则不用考虑,依然使用只考虑它之前那些物品的方案
} else {
//value[i][j] = Math.max(value[i - 1][j], v[i] + value[i - 1][j - w[i]]);
//当value[i - 1][j] < v[i] + value[i - 1][j - w[i]] 时,也就是新策略比旧策略好时,记录新策略用到的物品
//从之前的记录中找到没加入新物品时,在容量为最大容量 - 新物品重量 时使用的策略 + 新物品
if(value[i - 1][j] > v[i] + value[i - 1][j - w[i]]){
value[i][j] = value[i - 1][j];
}
else{
value[i][j] = v[i] + value[i - 1][j - w[i]];
g[i][j] = 1;//记录在容量为 j, 考虑前 i 个物品时,最优策略中物品i被放入了。
}
}
}
}
for(int i = 0; i < value.length; i++){
for(int j =0; j < value[i].length; j++){
System.out.print(value[i][j] + " ");
}
System.out.println();
}
System.out.println("-------------------------------------------------");
for(int i = 0; i < g.length; i++){
for(int j =0; j < g[i].length; j++){
System.out.print(g[i][j] + " ");
}
System.out.println();
}
//显示物品加入情况的表
//i和 j 是是背包可用容量为 j 时 ,最优解是否用到了物品 i, 但是因为我们考虑的时候中间每次都会放入数组,所以需要从后往前遍历;
//当发现最后结果中有某一个物品被放入时,就把最大可用容量变成 当前最大可用容量 - 被放入物品的重量,然后继续继续往前寻找
// 0 0 0 0 0
// 0 1 1 1 1
// 0 0 0 0 1
// 0 0 0 1 1
//最开始先看最后一排,发现 g[4][3] 是 1,说明最大背包容量为 4 时,最终策略中放入了物品3
//这时候可以知道,最优策略中有物品3,且除了物品3,剩余空间可能也是使用了最优策略。
//除去物品3,背包剩余容量为1,从g[4-3][2] 开始继续往前找。 g[1][2] 是0, 说明剩余空间的最优策略中没有物品2,继续往前找 g[1][1]
//g[1][1] = 1 说明拿掉物品3后,背包剩余容量为1时,最优策略中有物品1。
//除去物品1, 背包剩余容量为0, 继续往前寻找, g[0][0] = 0, 此时走到了尽头,说明没有其他物品被放入。.
int i = g.length - 1;
int j = g[0].length - 1;
while(i > 0 && j > 0){
if(g[i][j] == 1){
System.out.println("第" + i + "个物品被放入了背包");
j = j - w[i];
}
i--;
}
return value[v.length - 1][capacity];
}
}
0-1背包
设n个物体,体积v,价值w,背包总容量M
状态转移方程:f[j]=max(f[j],f[j-v[i]]+w[i])
代码:
for(int i=1;i<=n;i++)
{
for(int j=M;j>=v[i];j--)
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
完全背包
状态转移方程:f[j]=max(f[j],f[j-v[i]]+w[i])
代码:
for(int i=1;i<=n;i++)
{
for(int j=v[i];j<=M;j++)
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
多重背包
多重背包也和前两个类似,只不过多重背包是一个物品最可以取k次,然后求最大收益
一个很容易想到的点就是将第i个物品转化为k次,当做0-1背包去做,但是这样时间复杂度偏高,因此我们可以对此进行优化
二进制分组优化代码:
int cnt= 0;
for (int i = 1; i <= n; i++) {
int c = 1, v, w, k;
cin >> v >> w >> k;
while (k - c > 0) {
k -= c;
list[++cnt].w = c * w;
list[cnt].v = c * v;
c *= 2;
}
list[++cnt].w = p * w;
list[cnt].v = h * v;
}
这样就可以将k个压缩为log2(k)个,再套用0-1背包即可
混合背包
如果是0-1背包,就套用0-1背包代码
如果完全背包,就套用完全背包代码
如果是多重背包,就套用多重背包代码...
二维费用背包
在0-1背包基础上在增加一维即可
代码:
for(int i=1;i<=n;i++)
{
for(int j=M;j>=v[i];j--)
{
for(int k=T;k>=t[i];k--)
{
f[j][k]=max(f[j][k],f[j-v[i]][j-t[k]]+w[i]);
}
}
}
分组背包
将物品分组,每个组内的物品相互冲突,只能选一个
对每一组进行一次0-1背包
代码:
for(int i=1;i<=n;i++)//循环每一组
{
for(int j=M;j>=0;j--)//循环背包容量
{
for(int k=1;k<=cnt[k];k++)//循环组内每个物品
{
if(j>=v[t[i][j]])//如果背包容量大于该物品体积
{
f[j]=max(f[j],f[j-v[t[i][k]]+w[t[i][k]]);//0-1背包状态方程
}
}
}
}