> 文档中心 > 【动态规划】01背包问题+回溯查找背包物品(保姆级、入门级)

【动态规划】01背包问题+回溯查找背包物品(保姆级、入门级)

目录

一、0-1背包问题

二、问题分析

1、确定备忘录的具体含义

2、状态转移方程

3、初始化

4、遍历顺序及输出

5、回溯法求解最大价值时的背包物品 

三、总结 

四、完整代码

五、结语

一、0-1背包问题

给定gif.latex?n种物品(每种物品均只有一个)和一背包。物品i的重量是gif.latex?Wi,其价值为gif.latex?Vi,背包的最大容量为gif.latex?c。怎样选择装入背包中的物品,使得其总价值最大?

例如:现有4种物品,其对应的重量和价值如图所示,另有一最大容量为5的背包,求该背包所能装下物品的最大价值?

物品  重量 价值
0 2 4
1 1 3
2 4 6
3 3 5

二、问题分析

1、确定备忘录的具体含义

dp[i][j]:任取第0~i件物品,放入容量为j的背包,能得到的最大价值例:dp[1][2]=3的含义:任取物品0~物品1,放入容量为2的背包,能得到的最大价值(取物品1放入背包,其价值最大,为3)

dp[i][j] 的定义十分重要,状态方程的书写、数组初始化、遍历顺序和输出结果都必须严格按照该定义进行书写

2、状态转移方程

对于物品gif.latex?i,它只有放与不放两种状态,当它能放入背包时,状态转移方程只需取两种状态的最大值; 否则,取不放入时的最大价值。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAd3VzaGVuZ19oYXBweQ==,size_20,color_FFFFFF,t_70,g_se,x_16

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]);

 3、初始化

从状态转移方程可知,欲求dp[i][j],需先确定dp[i-1][j]和dp[i-1][j-w[i]]的值(即需保证dp[i][j]的左上角数据已经处理完成),故我们需对第0行和第0列进行初始化.

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAd3VzaGVuZ19oYXBweQ==,size_20,color_FFFFFF,t_70,g_se,x_16

//初始化第0列(即背包容量为0)for (i = 0; i < n; i++)dp[i][0] = 0;
//初始化第0行(即只有物品0)for (j = w[0]; j <= max_weight; j++) {if (w[0] <= j) dp[0][j] = v[0];elsedp[0][j] = 0;}

4、遍历顺序及输出

遍历顺序:求解dp[i][j]只需提前知道其左上角的数据,故以下两种遍历方式均可。

1、先遍历背包再遍历物品(即物品数固定,背包容量逐增至最大,物品再加1)

 2、先遍历物品再遍历背包(即背包容量固定,物品数逐增至最大,背包容量再加1)

输出:dp[n-1][max_weight]: n件物品任取,放入容量为max_weight的背包,所得的最大价值。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAd3VzaGVuZ19oYXBweQ==,size_20,color_FFFFFF,t_70,g_se,x_16

//先遍历物品再遍历背包for (i = 1; i < n; i++) {    //遍历物品for (j = 1; j <= max_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]);}}//先遍历背包再遍历物品for (j = 1; j <= max_weight; j++) {    //遍历背包for (i = 1; i < n; i++) {   //遍历物品if (j < w[i])dp[i][j] = dp[i - 1][j];elsedp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);}}

5、回溯法求解最大价值时的背包物品 

由状态转移方程(dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]))可知,当dp[i][j]\neqdp[i-1][j]时,物品i被放入背包中(判断条件),当dp[i][j]=0时,背包中已经没有物品(结束条件)。

//回溯法求解装入物品i--; j--;     //循环结束后,i=n,j=max_weight+1,故使用dp[n - 1][max_weight]须自减1cout << "最大价值时的背包物品为:" <=0) {    if (dp[i][j] != dp[i - 1][j]) {cout << "物品" << i << endl;j -= w[i];  //装入物品i后背包的最大容量}i--;    //物品i已经处理完成,接下来讨论物品i-1}

1、 使用dp[i][j](最大价值)前一定要对i,j进行处理,否则,运行时会抛出异常;

2、结束条件一定要加上i>=0,否则,运行结果可能会出现物品-1等错误。

3、该法得到的结果只为其中的一组最优解,可能还存在其他最优解,例如:当我们装入物品0(2,4)和物品3(3,5)时,其最大价值也为9.

三、总结 

 1、0-1背包问题是背包问题的基础。深入了解0-1背包问题(每件物品只有一个)是掌握完全背包问题(每件物品有无数个)、多重背包问题(不同物品数量不同)和分组背包(物品分为多组,每组最多选一个)的关键;

2、使用dp[i][j]数组时,一定要注意数组的界限问题,避免越界访问造成错误。比如:刚开始的时候,我认为背包不存在容量为0的情况,故没有将第一列初始化为0,导致输出出错,将dp[i][j]全部打印出来后,才知道是dp[1][1]出错了,因为dp[1][1]=max(dp[0][1],dp[0][0]+v[0]),需要用到dp[0][0],造成越界。所以,检验算法的正确性,可以先人工求解出dp[i][j]数组,再用程序将其打印出来,这样有利于我们快速找到问题所在。

1e96f5966f5f45099dcf8a9880f4b971.png

                                                                  转换表(打印)

转换表(人工)
  0 1 2 3 4 5
0 0 0 4 4 4 4
1 0 3 4 7 7 7
2 0 3 4 7 7 9
3 0 3 4 7 8 9

3、背包问题一定要先明确辅助数组的具体含义再确定转换方程,根据转换方程来确定初始化、遍历顺序和输出结果。

四、完整代码

#include#includeusing namespace std;const int max_weight = 5;const int n = 4;int main() {int i, j;int w[n] = { 2,1,4,3 }; //重量int v[n] = { 4,3,6,5 }; //价值int dp[n][max_weight+1];//辅助数组//初始化第0列(即背包容量为0)for (i = 0; i < n; i++)dp[i][0] = 0;//初始化第0行(即只有物品0)for (j = 1; j <= max_weight; j++) {if (w[0] <= j) dp[0][j] = v[0];elsedp[0][j] = 0;}//状态转移//先遍历物品再遍历背包for (i = 1; i < n; i++) {    //遍历物品for (j = 1; j <= max_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[n - 1][max_weight] << endl;//回溯法求解装入物品i--; j--;     //循环结束后,i=n,j=max_weight+1,故使用dp[n - 1][max_weight]须自减1cout << "最大价值时的背包物品为:" <=0) {    if (dp[i][j] != dp[i - 1][j]) {cout << "物品" << i << endl;j -= w[i];  //装入物品i后背包的最大容量}i--;    //物品i已经处理完成,接下来讨论物品i-1}return 0;}

运行结果: 

五、结语

当你想在你的代码中找到一个错误时,这很难;当你认为你的代码是不会有错误时,这就更难了。——Steve McConnell 《代码大全》

永远学习!永远进步!

码字不易,求个三连!

有问题也可以在评论区留言,有错误也欢迎各位大佬斧正,感激不尽!

关注我!后续会继续更新动态规划的问题分析,帮助你全面掌握!