问题描述
已知:有一个容量为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
来源:https://www.cnblogs.com/qie-wei/p/10160166.html