问题描述
已知:有一个容量为V的背包和N件物品,第i件物品的重量是weight[i],价值是cost[i]。问:在不超过背包容量的情况下,最多能获得多少价值。
01背包的特点:每种物品只有一件,可以选择放或者不放
1子问题:f[i][v] 表示前i件物品放到一个容量为v的背包中可以获得最大价值
2.状态转移方程:f[i][v] = max(f[i - 1][v],f[i - 1][v - weight[i]] + cost[i])
1) 如果第i件物品不放入背包中,那么问题就转换为:将前i - 1件物品放到容量为v的背包中,带来的收益f[i - 1][v]
2) 如果第i件物品能放入背包中,那么问题就转换为:将前i - 1件物品放到容量为v - weight[i]的背包中,带来的收益f[i - 1][v - weight[i]] + cost[i]
代码【01背包二维数组实现】
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
int N,V; //物品个数 ,背包最大容量
int weight[100],value[100];//物品重量 ,物品价值
int f[100][100] = {{0}};
/*
目标:在不超过背包容量的情况下,最多能获得多少价值
子问题状态:f[i][j]:表示前i件物品放入容量为j的背包得到的最大价值
状态转移方程:f[i][j] = max{f[i - 1][j],f[i - 1][j - weight[i]] + value[i]}
初始化:f数组全设置为0
*/
int Knapsack()
{
//初始化
memset(f,0,sizeof(f));
freopen("input.txt","r",stdin);
cin>>N>>V;
for(int i=1;i<=N;i++)
cin>>weight[i];
for(int i=1;i<=N;i++)
cin>>value[i];
//递推
for (int i = 1;i <= N;i++) //枚举物品
{
for (int j = 0;j <= V;j++) //枚举背包容量
{
if(j < weight[i])
f[i][j] = f[i - 1][j];
else
f[i][j] = max(f[i - 1][j],f[i - 1][j - weight[i]] + value[i]);
}
}
return f[N][V];
}
int main()
{
cout<<Knapsack()<<endl;
return 1;
}
滚动优化:上述的方法,我们使用二维数组 f[i][v] 保存中间状态,这里我们可以使用一维数组f[v]保存中间状态就能得到结果
第i次循环后,f[v]中存储的是前i个物体放到容量v时的最大价值只不过针对不同的i,f[v]一直在重复使用,所以,也会出现第i次循环可能会覆盖第i - 1次循环的结果。
1. for i=1..N //枚举物品
2. for v=V..0 //枚举容量,从大到小
3. f[v]=max{f[v],f[v-weight[i]] + cost[i]};
逆序枚举容量的原因:
注意一点,我们是由第 i - 1 次循环的两个状态(f[v],f[v - weight[i]])推出 第 i 个状态的,而且 v > v - weight[i],则对于第i次循环,背包容量只有当V..0循环时,才会先处理背包容量为v的状况,后处理背包容量为 v-weight[i] 的情况。
具体来说,由于,在执行v时,还没执行到v - weight[i]的,因此,f[v - weight[i]]保存的还是第i - 1次循环的结果。即在执行第i次循环 且 背包容量为v时,此时的f[v]存储的是 f[i - 1][v] ,此时f[v-weight[i]]存储的是f[i - 1][v-weight[i]]。
相反,如果在执行第 i 次循环时,背包容量按照0..V的顺序遍历一遍,来检测第 i 件物品是否能放。此时在执行第i次循环 且 背包容量为v时,此时的f[v]存储的是 f[i - 1][v] ,但是,此时f[v-weight[i]]存储的是f[i][v-weight[i]]。
因为,v > v - weight[i],第i次循环中,执行背包容量为v时,容量为v - weight[i]的背包已经计算过,即f[v - weight[i]]中存储的是f[i][v - weight[i]]。即,对于01背包,按照增序枚举背包容量是不对的。
代码【01背包一维数组实现】
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int N,V; //物品个数 ,背包最大容量
int weight[100],value[100];//物品重量 ,物品价值
int f[100]={0};
int Knapsack()
{
memset(f,0,sizeof(f));
for (int i = 1;i <= N;i++) //枚举物品
{
for (int j = V;j >= weight[i];j--) //枚举背包容量,防越界,j下限为 weight[i]
{
f[j] = max(f[j],f[j - weight[i]] + value[i]);
}
}
return f[V];
}
int main()
{
freopen("input.txt","r",stdin);
cin>>N>>V;
for(int i=1;i<=N;i++)
cin>>weight[i];
for(int i=1;i<=N;i++)
cin>>value[i];
cout<<Knapsack()<<endl;
return 1;
}
增序枚举背包容量会达到的效果:它会重复的装入某个物品,而且尽可能多的,使价值最大。
而逆序枚举背包容量:背包中的物品至多装一次,使价值最大。
以上是在不超过背包容量的情况下讨论的,在恰好装满背包的情况,需要注意初始化细节问题::
二维:除了f[i][0] = 0(第一列)外,其他全为负无穷。
一维:除了f[0] = 0,其他全为负无穷。
代码【二维】
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int MinNum = 0x80000000;//负无穷
int N,V; //物品个数 ,背包最大容量
int weight[100],value[100];//物品重量 ,物品价值
int f[100][100];
/*
目标:在恰好装满背包的情况下,最多能获得多少价值
初始化:除了f[i][0] = 0(第一列)外,其他全为负无穷
*/
int Knapsack()
{
//初始化
for (int i = 0;i <= N;i++)
for (int j = 0;j <= V;j++)
f[i][j] = MinNum;
for (int i = 0;i <= N;i++)
f[i][0] = 0;
for (int i = 1;i <= N;i++) //枚举物品
{
for (int j = 1;j <= V;j++) //枚举背包容量
{
if(j < weight[i])
f[i][j] = f[i - 1][j];
else
f[i][j] = max(f[i - 1][j],f[i - 1][j - weight[i]] + value[i]);
}
}
return f[N][V];
}
int main()
{
freopen("input.txt","r",stdin);
cin>>N>>V;
for(int i=1;i<=N;i++)
cin>>weight[i];
for(int i=1;i<=N;i++)
cin>>value[i];
cout<<Knapsack()<<endl;//输出25
return 1;
}
代码【一维】
#include <iostream>
#include <cstdio>
using namespace std;
const int MinNum = 0x80000000;//int最小的数
int N,V; //物品个数 ,背包最大容量
int weight[100],value[100];//物品重量 ,物品价值
int f[100];
/*
目标:在恰好装满背包容量的情况下,最多能获得多少价值
初始化:除了f[0] = 0,其他全为负无穷
*/
int Knapsack()
{
for (int i = 0;i <= V;i++)
f[i] = MinNum;
f[0]=0;
//递推
for (int i = 1;i <= N;i++) //枚举物品
{
for (int j = V;j >= weight[i];j--) //枚举背包容量,防越界,j下限为 weight[i]
{
f[j] = max(f[j],f[j - weight[i]] + value[i]);
}
}
return f[V];
}
int main()
{
freopen("input.txt","r",stdin);
cin>>N>>V;
for(int i=1;i<=N;i++)
cin>>weight[i];
for(int i=1;i<=N;i++)
cin>>value[i];
cout<<Knapsack()<<endl;//输出25
return 1;
}
input.txt测试案例:
3 5
3 2 2
5 10 20