http://t.csdnimg.cn/P7R3G
之前我们介绍了01背包,但是dp
数组是二维化的,现在我们需要将其变成一维数组,如果已经对二维化的01背包十分了解了,那么理解一维化的dp
数组也不是问题。
目录
分析
遍历顺序
原二维遍历
一维倒序遍历
代码
分析
我们再拿这个表格:
图中i
为物品序号,value[i]
为物品价值,weight[i]
为物品重量。
j
为背包大小。
原dp
数组的推导公式是
dp[i][j]=max(dp[i-1][j-1],value[i]+dp[i-1][j-weight[i]]);
即在选与不选中抉择,如果不选,那么总价值就是当前大小背包的原价值(即只考虑当前物品前的所有物品),
如果选,那么总价值就是该物品价值加上剩余空间背包的最优解。
那我们不妨将dp
数组想象成一个滚动的一维数组,每次对一个物品进行各种大小背包的分析,然后再对下一个物品进行各种大小背包的分析,每次均保留最优解。
这样下来,这个一维化的数组就是对所有已分析过的物品的最优解了。
遍历顺序
原二维遍历
我们可以发现,在原二维数组里,我们是在dp[i-1][j-1]
和value[i]+dp[i-1][j-weight[i]]
中选取最大值,因为不考虑当前物品,所以我们是在dp[i-1]
范围内选取,即在当前物品前面的所有已分析物品中。
一维倒序遍历
在一维数组里,我们没有[i]
这个维度,那我们如何找到“当前物品前面的所有已分析物品中”这个范围呢?
答案很简单,我们的一维数组是考虑完前面所有物品的最优解,本身就是“当前物品前面的所有已分析物品中”的范围,所以我们只需要在原数值和新数值中抉择即可:
dp[j]=max(dp[j],value[i]+dp[j-weight[i]]);
但是如果我们按照背包从小打大的顺序遍历,即按照从0到背包最大容量的顺序写的话,前面的较小背包就会考虑进当前物品,当后面的背包需要dp[j-weight[i]]
的时候,就算两遍当前物品i
,所以我们需要按背包容量从大到小遍历。
这时就会有疑问了,那如果按照背包容量从大到小遍历,那遇到dp[j-weight[i]]
的情况,需要找小背包怎么办,这时候就需要回过头想想,我们的一维dp
数组保存的就是考虑了当前物品前所有物品的最优解,就是我们需要的dp[j-weight[i]]
。
代码
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
int bag(vector<int>& weight,vector<int>& value,int bag)
{
//初始化
vector<int> dp(bag+1,0);
//dp[j]=max(dp[j]+value[i]+dp[j-weight[i]]);
//初始化
dp[0]=0;
//遍历顺序
for(int i=0;i<weight.size();i++)
{
for(int j=bag;j>=0;j--)
{
/*if(weight[i]>j)
{
dp[j]=dp[j];
}*/
if(weight[i]<=j)
{
dp[j]=max(dp[j],value[i]+dp[j-weight[i]]);
}
cout<<dp[j]<<' ';
}
cout<<endl;
}
return dp[bag];
}
};
int main() {
std::cout << "Hello World!\n";
vector<int> weight={1,3,4};
vector<int> value={15,20,30};
int bag=4;
Solution so;
cout<<so.bag(weight, value, bag);
}