0-1背包
1. 问题描述
【0-1背包——问题描述】有n件物品和一个最多能背重量为w的背包。第i件物品的重量时weight[i],得到的价值为value[i]。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
注:每个物品只有两种状态,取或者不取
2. 0-1背包——二维数组
【举例】背包的最大容量为4,物品的重量和价值如下
重量 | 价值 | |
---|---|---|
物品 0 | 1 | 2 |
物品 1 | 2 | 3 |
物品 2 | 3 | 4 |
2.1 状态分析
使用二维数组f[i][j]
: 表示从下标为 [0-i]
的物品里任意放取,放进容量为 j
的背包,其价值总和最大为f[i][j]
。
2.2 转移方程
f[i][j]
的值可以有两个方向推出
-
不放入物品i:此时
f[i][j] = f[i-1][j]
。当物品i的重量大于背包j
的重量时,物品i
无法放入背包,所以背包的价值仍然和前面一个相同。当背包重量为1时,只能装入物品0,物品1和物品2无法装入,故背包价值仍等于前面一个的价值
-
放入物品i:此时
f[i][j] = f[i-1][j-weight[i]] + value[i]
。当前背包的价值 = 没有加入物品i时背包的最大价值 + 物品i的价值
。f[i - 1][j - weight[i]]
为背包容量为j - weight[i]
的时候不放物品i的最大价值,那么f[i - 1][j - weight[i]] + value[i] (物品i的价值)
,就是背包放物品i得到的最大价值
2.3 初始化
-
如果背包容量为0,无论取哪些物品,背包所装物品的最大价值一定为0,故
f[i][0] = 0
; -
根据转移方程
f[i][j] = max(f[i - 1][j], f[i - 1][j - weight[i]] + value[i]);
可以看出i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。- f[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值。
- 当背包容量小于物品0的重量时,背包所存放物品的最大价值为0;即
当j < weight[0] 时,f[0][j] = 0;
- 当背包容量大于等于物品0的重量时,背包可以存放此物品,故背包所存放物品的最大价值为物品0的价值,即
当j >= weight[0]时,f[0][j] = value[0];
for(int j = 0;j < weight[0];++j){ //若数组f[i][j]预先初始化为0,此步可省略 f[0][j] = 0; } for(int j = weight[0];j <= bagweight;++j){ f[0][j] = value; }
2.4 计算顺序
先遍历背包与先遍历物品一样
2.5 举例推导数组f[i][j]
2.5 C++实现
【算法实现】
int bag01(vector<int>& weight,vector<int>& value){
int bagweight = 4; //背包最大容量
vector<vector<int>> f(weight.size(),vector<int>(bagweight+1,0));
//初始化
for(int j = weight[0];j <= bagweight;++j){
f[0][j] = value[0];
}
for(int i = 1;i < weight.size();++i){
for(int j = 0;j <= bagweight;++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[weight.size()-1][bagweight];
}
【测试】
#include <iostream>
#include <vector>
using namespace std;
int main(){
vector<int> weight = {1,2,3};//物品重量
vector<int> value = {2,3,4};//物品价值
cout << bag01(weight,value) << endl;
return 0;
}
3. 0-1背包——一维数组
【思考】在使用二维数组进行计算时,转移方程:f[i][j] = max(f[i-1][j],f[i-1][j-weight[i]]+value[i])
,假若把f[i-1]层拷贝到f[i]层,表达式可以表示为f[i][j] = max(f[i][j],f[i][j-weight[i]]+value[i])
,则可以捡回为一维数组,只使用f[j]
.
3.1 确定状态
使用一维数组f[j]
:则表示为容量为j的背包所存放物品的最大价值为f[j]。
3.2 转移方程
f[i]
的取值有两个选择,
- 一个是未加上物品i时背包所存放物品的最大价值,即为前一个
f[j]
, - 另一个是加上物品i时背包所存放物品的最大价值,即
f[j-weight[i]] + value[i]
所以转移方程可以表示为:f[j] = max(f[j],f[j-weight[i]]+value[i]);
3.3 初始条件
f[j]
表示:容量为j
的背包,所背的物品价值可以最大为f[j]
,那么f[0]
就应该是0,因为背包容量为0所背的物品的最大价值就是0。
3.4 计算顺序
for(int i = 0; i < weight.size();++i){ //遍历物品
for(int j = bagweight;j >= weight[i];j--){ //遍历背包容量
f[j] = max(f[j],f[j-weight[i]]+value[i]);
}
}
背包容量倒序遍历是为了保证物品i只被放入一次。但如果正序遍历,那么物品0就会被重复加入多次。
遍历物品的顺序和遍历背包容量的顺序不能弄翻,否则每个f[j]就只会放入一个物品。
3.5 C++实现
【算法实现】
int bag02(vector<int>& weight,vector<int>& value){
int bagweight = 4;
vector<int> f(bagweight+1,0);
for(int i = 0;i < weight.size();++i){
for(int j = bagweight;j >= weight[i];j--){
f[j] = max(f[j],f[j-weight[i]]+value[i]);
}
}
return f[bagweight];
}
【测试】
#include <iostream>
#include <vector>
using namespace std;
int main(){
vector<int> weight = {1,2,3};
vector<int> value = {2,3,4};
cout << bag02(weight,value) << endl;
return 0;
}