01背包问题
有N件物品和最多可以装W的背包,第i件物品质量为weight[i],价值为value[i]。每个物品只用一次,求怎么装才能获得最大价值。
二维dp数组:
dp数组及下标含义:dp[i][j]
表示从下标为[0-i]
的物品里任意取放进容量为j的背包,价值总和的最大值。
递推公式:dp[i][j]
有两种方式推出来
- 不放物品i:由
dp[i-1][j]
推出,即背包容量为j,只在[0-(i-1)]
中取的最大价值总和。 - 放物品i:由
dp[i-1][j-weight[i]]+value[i]
,即考虑不放物品i,然后只在[0-(i-1)]
中选择,然后再将物品i
放回去。 - 所以递推公式为:
dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])
。
dp数组的初始化:
dp[i][0]=0
对于dp[0][j]
:如果j<weight[0]
时,还不够放入第0件,所以dp[0][j]=0
。
如果j>=weight[0]
,则刚好放入第0件,所以dp[0][j]=value[0]
。
完整代码:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main() {
vector<int> weight = {1,3,4};
vector<int> value = {15,20,30};
int all = 4;
vector<vector<int>> dp(weight.size(), vector<int>(all + 1,0));
for (int j = weight[0]; j < all; j++) dp[0][j] = value[0]; //初始化
for (int i = 0; i < weight.size(); i++) {
for (int j = 0; j <= all; j++) {
if (j < weight[i]) dp[i][j] = dp[i - 1][j];
else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
cout<<dp[weight.size() - 1][all];
}
方法二:滚动数组
使用二维dp时,递推公式为:
dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])
从递推公式可以看出,第i
行只与第i-1
行有关,最后我们也只要最后一个的数值,所以可以把第i
层的数据拷贝到第i
层,则递推公式可以变为dp[i][j]=max(dp[i][j],dp[i][j-weight[i]]+value[i])
。这样可以省去变量i
,就只用一个一维数组了。
但是计算j
时要用到j-weight[i]
,所以每一层需要从后开始遍历。
如果从前往后遍历,则会造成j
考虑了两次物品i
,因为在更新j-weiht[i]
时已经考虑了物品i
,在更新j
时要使用j-weight[i]
同时又考虑了一次物品i
,所以相当于考虑了两次物品i
。
dp数组及下标含义
dp[i]
表示容量为i
时可以装的最大价值总和。
递推公式
dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
dp数组初始化
if (j < weight[0]) dp[j] = 0;else dp[j] = value[0];
遍历顺序
for (1:weight.size()) //外层循环遍历物品 for (all:weight[i]) //内层循环遍历背包容量到weight[i]
完整代码
#include<iostream>
#include<vector>
#include<algorithm>using namespace std;
int main() {
vector<int> weight = {1,3,4};
vector<int> value = {15,20,30};
int all = 4;
vector<int> dp(all + 1, 0);
for (int j = weight[0]; j <= all; j++) dp[j] = value[0];
for (int i = 0; i < weight.size(); i++) {
for (int j = all; j >= weight[i]; j--) {
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
cout<<dp[all];
}