前言
接着上篇,接着总结。
二、完全背包及其解法
2.1 问题描述
完全背包问题和01背包问题框架很像,都是从一批物品中获取一些物品,这些物品的重量有一定的限制(不超过W),求的是哪种选择可以得到最大的Value。 唯一的不同点就在于完全背包中每种物品可以选择任意次(01背包每种物品只能是0或者1次)。
正式的描述如下图:
2.2 解法
关于递归搜索的解法这里就不说了,和01解法类似,稍有不同的就是如果背包当前剩余的W还可以装下一次目前的物品,那就继续尝试装。
下面主要介绍完全背包的DP解法。
2.2.1 未优化的DP解法
01背包问题中的重复子问题和最优子结构性质和01问题也类似,这里也就不说了。 有了01背包的基础,这里应该可以推出其状态定义和其转移方程。
哎,有点回眸一笑,似曾相识的感觉?
再把01背包问题的状态转移方程拿过来康康。
对,对于当前物品,选了之后还可不可以重复选就体现在这儿了。
还不懂,有点绕人?
动手画个状态矩阵推推。
还不懂?
enen…, 那就看看其他的资料吧。 这里推荐参考【1】。
有了上述的动态方程。 代码部分就很简单了。
/*
*@description 用DP解决完全背包问题
*@ dp[i][j] : 前i个物品中挑选出总重量不超过j时的总价值的最大值
*
*/
int ComPacketProblemByDp(){
vector<vector<int>> dp(n+1,vector<int>(W+1,0));
for(int i = 1;i<=n;++i){
for(int j = 0;j<=W;++j){
if(j < weight[i-1]){
dp[i][j] = dp[i-1][j];
}else{
dp[i][j] = max(dp[i-1][j],dp[i][j-weight[i-1]]+value[i-1]); //DP递推公式
}
}
}
return dp[n][W];
}
note:注意这里的下标区别。
2.2.2 状态压缩的DP解法
如果观察状态上述的状态转移矩阵的话,可以发现,每个dp[i+1][j]其实只用到了上面一层的数据(以前的状态),所以可以进行状态压缩,把二维状态压缩为一维的。
代码如下所示:
/*
*@description 用DP解决完全背包问题,这里用了空间压缩,把二维dp压缩成了一维dp
*@dp[i][j] : 前i个物品中挑选出总重量不超过j时的总价值的最大值
*@
*/
int ComPacketProblemByDp1(){
vector<int> dp(W+1,0);
for(int i = 1;i<=n;++i){
for(int j = weight[i-1];j<=W;++j){
dp[j] = max(dp[j],dp[j-weight[i-1]]+value[i-1]);
}
}
return dp[W];
}
三、总结
好了,终于到了激动人心的总结时刻了。
背包问题是动态规划中的经典问题,在学习背包问题的过程中,有了一些关于思考,罗列如下
(1)、对于动态规划来说,熟悉的题我们可能很快就能知道用动态规划进行解题。不熟悉的题,我觉得还是需要利用动态规划的本质,即利用动态规划本身的特点(重复子问题+最优子结构)来进行分析,看其是否可以重复利用以前的状态。
(2)、有人说,动态规划本质上是一种自带裁剪的搜索方法,我觉得有点道理。 有些题目可能有多个变量(比如说背包问题中的价值和重量),这种带多个变量的搜索问题(可以使用动态规划解题),有一个解题方向是控制变量,即控制其中的一个变量,让另一个变量“飞舞”。
(3)、还有些时候,可以从结果来找解题方式。 即最终的结果就那么几种,那么我们就可以遍历这几种状况,来判断是否可行。 背包问题的解法中有点这么个意思(最终背包里的W就那么几种情况)
动态规划的水很深,我刚学会狗刨,还得下水继续练啊。
参考
【1】、《挑战程序设计》