当前位置:   article > 正文

0-1背包问题详解-动态规划-两种方法

0-1背包问题详解-动态规划-两种方法

问题描述:

给定n种物品和一背包。物品i的重量为wi,其价值为vi, 背包容量为c。问应如何选择装入背包中的物品,使得背入背包的物品的总价值最大?

解析:

问题形式化的描述是,给定c > 0, wi, vi, 1 <= i <= n(c为背包容量), 要找出一个n元0-1向量(x1, x2, ... , xn),xi ∈ {0, 1}, 1 <= i <= n, 使得∑ wixi (i 从 1 到 n 求和)<= c ,而且∑ wixi (i 从 1 到 n 求和)达到最大。因此0-1背包问题是一个特殊的整数规划问题。

方法1:

递归关系:(这里与课本的描述不同个人感觉课本的“加”比较难理解, 这里用的“减”, 相信我继续看下去QAQ, 方法2用课本的方法“加”)

设所给0-1背包问题的子问题的最优值为m[i][j], 即m[i][j]的含义是是在背包容量为j,可选物品为1, 2, 3, ..., i 时的0-1背包问题的最优值。由0-1背包问题的最优子结构性质,可建立计算m[i][j]的递归式如下:

m[i][j] = max{m[i - 1][j], m[i - 1][j - w[i]] + v[i]}     j >= w[i];

            = m[i - 1][j]                                                    j < w[i];

递归关系流程化:

1:如果不放入第 i 件物品,则问题转换为“前i - 1件物品放入容量为 j 的背包中”;

2:如果放入第 i 件物品,则问题转化为“前i - 1件物品放入容量为 j - w[i] 的背包中”,此时的最大值为 max{m[i - 1][j], m[i - 1][j - w[i]] + v[i] }.

代码:

  1. /*
  2. 输入样例 1:
  3. 3 10
  4. 4 3
  5. 5 4
  6. 6 5
  7. 输出为:
  8. 最优解为 : 11
  9. 选择的物品的序号为 :
  10. 2 3
  11. 输入样例 2:
  12. 5 10
  13. 6 2
  14. 3 2
  15. 5 6
  16. 4 5
  17. 6 4
  18. 输出为:
  19. 最优解为 : 15
  20. 选择的物品的序号为 :
  21. 1 2 5
  22. */
  23. #include <bits/stdc++.h>
  24. using namespace std;
  25. const int MAX = 1024;
  26. int n; //物品个数
  27. int c; //包的容量
  28. int value[MAX]; //物品的价值
  29. int weight[MAX]; //物品的重量
  30. int x[MAX]; //n元0-1向量
  31. int m[MAX][MAX]; //解的容器
  32. void Input()
  33. {
  34. scanf("%d %d", &n, &c);
  35. for(int i = 1; i <= n; ++i)
  36. scanf("%d %d", &value[i], &weight[i]);
  37. }
  38. //创建最优解
  39. void Knapsack()
  40. {
  41. memset(m, 0, sizeof(m));
  42. for(int i = 1; i <= n; ++i) //逐行填表,i表示当前可选物品数,j表示当前背包的容量, 也就是从低到顶。
  43. {
  44. for(int j = 1; j <= c; ++j)
  45. {
  46. if(j < weight[i])
  47. m[i][j] = m[i - 1][j];
  48. else
  49. {
  50. m[i][j] = max(m[i - 1][j], m[i - 1][j - weight[i]] + value[i]);
  51. }
  52. }
  53. }
  54. }
  55. //获取最优解(即设法将求得的最优解输出出来)
  56. void Traceback()
  57. {
  58. int cc = c;
  59. for(int i = n; i > 1; --i)
  60. {
  61. if(m[i][cc] == m[i - 1][cc])
  62. x[i] = 0;
  63. else
  64. {
  65. x[i] = 1;
  66. cc -= weight[i];
  67. }
  68. }
  69. if(cc >= weight[1])
  70. x[1] = 1;
  71. }
  72. void Output()
  73. {
  74. cout << "最优解为 : " << m[n][c] << endl;
  75. cout << "选择的物品的序号为 :" << endl;
  76. for(int i = 1; i <= n; ++i)
  77. if(x[i] == 1)
  78. cout << i << " ";
  79. cout << endl;
  80. }
  81. int main()
  82. {
  83. Input();
  84. Knapsack();
  85. Traceback();
  86. Output();
  87. }

参考博客 :https://www.cnblogs.com/vincently/p/4804225.html, 补充了Traceback()求解的过程, 完善了代码。

方法2:(解释见代码注释)

递归关系:

设所给0-1背包问题的子问题的最优值为m[i][j], 即m[i][j]的含义是背包容量为j,可选物品为i, i + 1, ... n 时的0-1背包问题的最优值。由0-1背包问题的最优子结构性质,可建立计算m[i][j]的递归式如下:

m[i][j] = max{m[i + 1][j], m[i + 1][j - w[i]] + v[i]}     j >= w[i];

            = m[i + 1][j]                                                    j < w[i];

递归关系流程化:

1:如果不放入第 i 件物品,则问题转换为“i + 1, i + 2,  ..... , n件物品放入容量为 j 的背包中”;

2:如果放入第 i 件物品,则问题转化为“i + 1, i + 2,  ..... , n 件物品放入容量为 j - w[i] 的背包中”,此时的最大值为 max{m[i + 1][j],  m[i + 1][j - w[i]] + v[i]}.

与方法1的区别:

请先务必先看懂方法1~。

首先我们知道求最优解的过程就是填表m的过程。

 方法1是从第一行开始填表m,而方法2是从第n行开始填表m即(两种方法都是是从底往上求解,不过方法一是可选物品从1-> i(从左到右),方法二是可选物品从i -> n(从右向左)。这就是区别。而思路完全一样。请务必好好想想,然后就会恍然大悟٩(๑>◡<๑)۶ 


代码:

总的来说方法2比方法1运行速度快。

  1. /*
  2. 输入样例 1:
  3. 3 10
  4. 4 3
  5. 5 4
  6. 6 5
  7. 输出为:
  8. 最优解为 : 11
  9. 选择的物品的序号为 :
  10. 2 3
  11. 输入样例 2:
  12. 5 10
  13. 6 2
  14. 3 2
  15. 5 6
  16. 4 5
  17. 6 4
  18. 输出为:
  19. 最优解为 : 15
  20. 选择的物品的序号为 :
  21. 1 2 5
  22. */
  23. #include <bits/stdc++.h>
  24. using namespace std;
  25. const int MAX = 1024;
  26. int n; //物品个数
  27. int c; //包的容量
  28. int value[MAX]; //物品的价值
  29. int weight[MAX]; //物品的重量
  30. int x[MAX]; //n元0-1向量
  31. int m[MAX][MAX]; //解的容器
  32. void Input()
  33. {
  34. scanf("%d %d", &n, &c);
  35. for(int i = 1; i <= n; ++i)
  36. scanf("%d %d", &value[i], &weight[i]);
  37. }
  38. //创建最优解
  39. void Knapsack()
  40. {
  41. int jMax = min(c, weight[n] - 1);
  42. //这一块填最后m表的最后一行
  43. /*
  44. 解释一下就是:“在可选的物品只有n即最后一个物品n,包的容量为c时” 的最优解。
  45. 第一个for循环:
  46. 容易知道在背包容量为0 ~ weight[n] - 1的时候背包是放不进去物品n的,如果背包
  47. 的容量小于物品n的质量,背包也是放不进去物品的,所以从weight[n] - 1 和 c 中
  48. 选择一个较小的,然后m[n][0:jMax]的值为零
  49. 第二个for循环:
  50. 自然可知当背包容量大于weigh[n]的时候,由于其可选则的物品只有 物品n,因此m[n][weight[n]:c]
  51. 的值全部为value[n].
  52. */
  53. for(int j = 0; j <= jMax; ++j)
  54. m[n][j] = 0;
  55. for(int j = weight[n]; j <= c; ++j)
  56. m[n][j] = value[n];
  57. //这一块是填m表的2 ~ n - 1行,容易理解
  58. for(int i = n - 1; i > 1; --i)
  59. {
  60. jMax = min(c, weight[i] - 1);
  61. for(int j = 0; j <= jMax; ++j)
  62. m[i][j] = m[i + 1][j];
  63. for(int j = weight[i]; j <= c; ++j)
  64. m[i][j] = max(m[i + 1][j], m[i + 1][j - weight[i]] + value[i]);
  65. }
  66. //这里是填m表的第一行,好好理解一下,不难,好好考虑一下 φ(>ω<*)
  67. m[1][c] = m[2][c];
  68. if(c >= weight[1])
  69. m[1][c] = max(m[1][c], m[2][c - weight[1]] + value[1]);
  70. }
  71. //获取最优解(即设法将求得的最优解输出出来)
  72. void Traceback()
  73. {
  74. int cc = c;
  75. for(int i = 1; i < n; i++)
  76. if(m[i][cc] == m[i + 1][cc])
  77. x[i] = 0;
  78. else
  79. {
  80. x[i] = 1;
  81. cc -= weight[i];
  82. }
  83. x[n] = (m[n][cc]) ? 1 : 0;
  84. }
  85. void Output()
  86. {
  87. cout << "最优解为 : " << m[1][c] << endl;
  88. cout << "选择的物品的序号为 :" << endl;
  89. for(int i = 1; i <= n; ++i)
  90. if(x[i] == 1)
  91. cout << i << " ";
  92. cout << endl;
  93. }
  94. int main()
  95. {
  96. Input();
  97. Knapsack();
  98. Traceback();
  99. Output();
  100. // cout << "*******" << endl;
  101. // for(int i = 1; i <= n; ++i)
  102. // {
  103. // for(int j = 1; j <= c; ++j)
  104. // cout << m[i][j] << " ";
  105. // cout << endl;
  106. // }
  107. }

方法三:基于以方法一的路径压缩

思路:

定义m[j] : m[j]为背包容量为 j 时(注意不是剩余容量),背包装入的最大value。

解题流程:遍历 i (可选物品为 1 ~ i),m[j] = max{ m[j], m[j - weight[i]] + value[i] }

缺点:无法求出选中了哪些物品

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int MAX = 1024;
  4. int n; //物品个数
  5. int c; //包的容量
  6. int value[MAX]; //物品的价值
  7. int weight[MAX]; //物品的重量
  8. int x[MAX]; //n元0-1向量
  9. int m[MAX]; //解的容器
  10. void Input()
  11. {
  12. scanf("%d %d", &n, &c);
  13. for(int i = 1; i <= n; ++i)
  14. scanf("%d %d", &value[i], &weight[i]);
  15. }
  16. //创建最优解
  17. void Knapsack()
  18. {
  19. memset(m, 0, sizeof(m));
  20. for(int i = 1; i <= n; ++i)
  21. {
  22. for(int j = c; j >= weight[i]; --j)
  23. {
  24. m[j] = max(m[j], m[j - weight[i]] + value[i]);
  25. }
  26. }
  27. }
  28. void Output()
  29. {
  30. cout << "最优解为 : " << m[c] << endl;
  31. cout << endl;
  32. }
  33. int main()
  34. {
  35. Input();
  36. Knapsack();
  37. Output();
  38. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/429943
推荐阅读
相关标签
  

闽ICP备14008679号