一、问题描述
有n件物品,还有一个最多装重量为w的背包,第i件物品重量是w[i],其价值为v[i],每件物品只能装一次,那么怎样装物品,才能使装入背包物品中的价值总和最大。
其实这道题在各大题目网站上,应该都不会出现原题,但许多动态规划类题目的思路其实来源于此题,所以关键是掌握基础的,再将题目转化为01背包问题。
二、算法思路
其实这道题的最简单想法,那就是利用暴力回溯,枚举出所有的背包装物品的情况,但这显然于算法而言是一个非常费时间的解法,它的时间复杂度是指数级的,所以更需要使用动态规划。
那么我们依然用上一章节的动态规划做题四步法(不太清楚的友友们可以去看博主的上一篇博客)
1.明确dp数组以及其含义
这里我们的目的是为了得到背包装的最大值,那么这里dp[i][j]的含义为在[0-i]之间的物品选择,放进背包容量为[j],价值的最大数是多少?可能了解这道题的同学会觉得很绕,那这里放一张图方便大家的理解。
2.推导递推公式
其实递推公式的类型基本上就两种一种要么是求和(斐波那契),另一种就是这种求最大值。
在这里,我们要时刻牢记dp数组的含义,由此数组可以得出推导的大概方向,
1.不放这个物品——也就是0,那么此时dp[i][j]的大小就是上一个物品价值的最大值即dp[i][j] = dp[i-1][j],因为我没放东西,所以跟上一个相同。
2.放下这个物品——也就是1,那么此时dp[i][j]就需减去i物品的重量,并且加上物品的价值,dp[i][j] = dp[i-1][j-w[i]] + v[i].
那么递推公式就为 dp[i][j] = max(dp[i-1][j] , dp[i-i][j-w[i]]+v[i]).
3.初始化
关于动态规划的初始化,通俗的来讲,是将最底层的子问题给初始化掉,就像斐波那契数一样,需要求得dp[0]和dp[1]来求解,但其实也要根据dp数组含义来进行初始化。
所以我们回顾上面那张图,要将第一列和第一行给初始化掉,即dp[0][j]和dp[i][0],因为第一列,背包可重量为0,所以背包所能获得的价值也为0。
然后我们再看dp[0][j]随着可容重量的增加,接下来的价值就为v[0],即当j>=w[0]时,d[0][j]就为v[i]。
初始化情况如图:
4遍历顺序的确定
其实从图片来看,这道题可以从两个方向来遍历,物品和背包。那应该先遍历物品还是先遍历背包呢?其实两者都可以!
但个人觉得先遍历物品再去遍历背包会方便理解些,但发生这种情况是为什么,那就是因为递归最根本的性质问题。我们的递归公式 dp[i][j] = max(dp[i-1][j] , dp[i-i][j-w[i]]+v[i]). 可以看到是靠里面两个选来得出的。
三、代码
int main( )
{
vector<int> w = {1,4,6};
vector<int> v = {10,20,30};
int bag_weight = 6;
vector<vector<int>> dp(w.size(),vector<int>(bag_weight + 1,0));
for(int j = w[0];j <= bag_weight;j++)
dp[0][j] = v[0];
for(int I=0;i<w.size;i++)
for(int j = 0;j<=bag_weight;j++)
if(j<w[I])
dp[i][j] = dp[i-1][j];
else
dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]] + v[i];
cout << dp[w.size-1][bag_weight];
return 0;
}
四、总结
做动态规划类的题目,其实可以多在纸上推导递推过程,这样既能提高效率,又能提高准确率。做法题很切忌纸上谈兵,瞎想,到头来啥都想不出,自己也要多在程序里测试dp数组的正确性。