当前位置:   article > 正文

背包问题之完全背包算法详解_完全背包问题

完全背包问题

一、完全背包问题描述

有5种物品和1个背包,每种物品的个数是无限的,背包最多只能装下10公斤的物品。怎样选择物品,使得背包能装下并且得到的价值最大。物品的重量、价值如下所示:

物品编号重量价值
126
223
365
454
546


二、解题思路

我们先看下多重背包实现原理:背包问题之多重背包_爱思考的实践者的博客-CSDN博客

对比分析发现,完全背包与多重背包的差别就是:对物品按种类划分,每种物品的个数不限制。

可以对问题进行抽象:对物品按顺序编号,物品i的重量为weight[i],价值为value[i],个数不限制。选取第i种物品时,已知背包当前最大承重为j,怎样装载物品,才能使得背包最大价值dp[i][j]最大?

在多重背包状态转移方程的基础上,可以总结出多重背包的状态转移方程:

当前物品i的重量weight[i]大于背包承重j时,背包最大价值为:

dp[i][j] = dp[i-1][j]

当前物品i的重量weight[i]小于等于背包承重j时,背包最大价值为:

dp[i][j] = Math.max(dp[i-1][j - k*weight[i]] + k * value[i], dp[i-1][j])     

其中,k满足条件: 0 <= k <= j/weight[i]。

三、Java编码实现

根据上一节的状态转移方程,我们很容易就能编程解决多重背包问题。

3.1 朴素求解背包最大价值与选取物品列表

直接按照状态转移方程进行求解,具体实现代码为:

  1. package com.test.packalgorithm;
  2. import com.google.common.collect.Maps;
  3. import org.apache.commons.collections4.map.MultiKeyMap;
  4. import java.util.Map;
  5. import java.util.Objects;
  6. /**
  7. * 多重背包
  8. */
  9. public class CompletePackRecord {
  10. /**
  11. * 获取最大价值
  12. *
  13. * @param N 物品种类数
  14. * @param W 背包最大承重
  15. * @param weight 物品重量数组
  16. * @param value 物品价值数组
  17. * @param ij2Goods 选择的商品列表
  18. * @return 最大价值
  19. */
  20. public int[][] getDp(int N, int W, int[] weight, int[] value, MultiKeyMap<Integer, Map<Integer, Integer>> ij2Goods) {
  21. // 定义一个数组dp[i][j] i表示当前物品的种类序号, j表示当前书包的重量
  22. int[][] dp = new int[N + 1][W + 1]; // 【物品种类, 背包容量】
  23. for (int j = 0; j <= W; j++) { // 物品不存在时,最大价值肯定是0
  24. dp[0][j] = 0;
  25. }
  26. for (int i = 1; i <= N; i++) { // 背包重量为0时,最大价值肯定是0
  27. dp[i][0] = 0;
  28. }
  29. for (int i = 1; i <= N; i++) { // 从第1类物品开始选
  30. for (int j = 1; j <= W; j++) {
  31. // 初始化 dp[i][j]
  32. dp[i][j] = dp[i - 1][j];
  33. Map<Integer, Integer> preGoods = ij2Goods.get(i - 1, j);
  34. if (Objects.isNull(preGoods)) {
  35. preGoods = Maps.newHashMap();
  36. }
  37. if (weight[i] <= j) { // 第i类物品重量 小于等于 当前承载重量,根据价值大小判断是否放入。
  38. // 考虑物品的件数限制, 寻找dp[i][j]的最大值
  39. int maxNumber = j / weight[i];
  40. for (int k = 0; k <= maxNumber; k++) {
  41. int ijkValue = dp[i - 1][j - (k * weight[i])] + (k * value[i]);
  42. dp[i][j] = Math.max(dp[i][j], ijkValue);
  43. }
  44. if (dp[i][j] > dp[i - 1][j]) {
  45. int k;
  46. for (k = 0; k <= maxNumber; k++) {
  47. int ijValue = dp[i - 1][j - (k * weight[i])] + (k * value[i]);
  48. if (dp[i][j] == ijValue) {
  49. break;
  50. }
  51. }
  52. preGoods = ij2Goods.get(i - 1, j - (k * weight[i]));
  53. if (Objects.isNull(preGoods)) {
  54. preGoods = Maps.newHashMap();
  55. }
  56. Map<Integer, Integer> goods = Maps.newHashMap();
  57. goods.putAll(preGoods);
  58. goods.put(i, k);
  59. ij2Goods.put(i, j, goods);
  60. } else {
  61. ij2Goods.put(i, j, preGoods);
  62. }
  63. } else { // 第i件物品重量大于当前承载重量,则不放入。
  64. ij2Goods.put(i, j, preGoods);
  65. }
  66. }
  67. }
  68. return dp;
  69. }
  70. public static void main(String[] args) {
  71. int N = 5; // 商品种类数
  72. int W = 10; // 背包最大承载重量
  73. int[] w = new int[N + 1]; // 每件物品的重量,为方便理解,下标从1开始
  74. w[1] = 2;
  75. w[2] = 2;
  76. w[3] = 6;
  77. w[4] = 5;
  78. w[5] = 4;
  79. int[] v = new int[N + 1]; // 每件物品的价值
  80. v[1] = 6;
  81. v[2] = 3;
  82. v[3] = 5;
  83. v[4] = 4;
  84. v[5] = 6;
  85. MultiKeyMap<Integer, Map<Integer, Integer>> ij2Goods = new MultiKeyMap<>();
  86. CompletePackRecord obj = new CompletePackRecord();
  87. int[][] dp = obj.getDp(N, W, w, v, ij2Goods);
  88. for (int i = 0; i <= N; i++) {
  89. for (int j = 0; j <= W; j++) {
  90. System.out.printf("(%d,%d)=%-5d", i, j, dp[i][j]);
  91. }
  92. System.out.println();
  93. }
  94. // 背包能够装入物品的最大值为
  95. int maxValue = dp[N][W];
  96. System.out.printf("maxValue=%d", maxValue);
  97. System.out.println();
  98. for (int i = 1; i <= N; i++) {
  99. for (int j = 1; j <= W; j++) {
  100. System.out.printf("(%d,%d)=%-8s", i, j, ij2Goods.get(i, j).toString());
  101. }
  102. System.out.println();
  103. }
  104. System.out.println("goods:");
  105. ij2Goods.get(N, W).forEach((key, value)-> System.out.printf("key=%d, value=%d\n", key, value));
  106. }
  107. }

运行结果为:

  1. (0,0)=0 (0,1)=0 (0,2)=0 (0,3)=0 (0,4)=0 (0,5)=0 (0,6)=0 (0,7)=0 (0,8)=0 (0,9)=0 (0,10)=0
  2. (1,0)=0 (1,1)=0 (1,2)=6 (1,3)=6 (1,4)=12 (1,5)=12 (1,6)=18 (1,7)=18 (1,8)=24 (1,9)=24 (1,10)=30
  3. (2,0)=0 (2,1)=0 (2,2)=6 (2,3)=6 (2,4)=12 (2,5)=12 (2,6)=18 (2,7)=18 (2,8)=24 (2,9)=24 (2,10)=30
  4. (3,0)=0 (3,1)=0 (3,2)=6 (3,3)=6 (3,4)=12 (3,5)=12 (3,6)=18 (3,7)=18 (3,8)=24 (3,9)=24 (3,10)=30
  5. (4,0)=0 (4,1)=0 (4,2)=6 (4,3)=6 (4,4)=12 (4,5)=12 (4,6)=18 (4,7)=18 (4,8)=24 (4,9)=24 (4,10)=30
  6. (5,0)=0 (5,1)=0 (5,2)=6 (5,3)=6 (5,4)=12 (5,5)=12 (5,6)=18 (5,7)=18 (5,8)=24 (5,9)=24 (5,10)=30
  7. maxValue=30
  8. (1,1)={} (1,2)={1=1} (1,3)={1=1} (1,4)={1=2} (1,5)={1=2} (1,6)={1=3} (1,7)={1=3} (1,8)={1=4} (1,9)={1=4} (1,10)={1=5}
  9. (2,1)={} (2,2)={1=1} (2,3)={1=1} (2,4)={1=2} (2,5)={1=2} (2,6)={1=3} (2,7)={1=3} (2,8)={1=4} (2,9)={1=4} (2,10)={1=5}
  10. (3,1)={} (3,2)={1=1} (3,3)={1=1} (3,4)={1=2} (3,5)={1=2} (3,6)={1=3} (3,7)={1=3} (3,8)={1=4} (3,9)={1=4} (3,10)={1=5}
  11. (4,1)={} (4,2)={1=1} (4,3)={1=1} (4,4)={1=2} (4,5)={1=2} (4,6)={1=3} (4,7)={1=3} (4,8)={1=4} (4,9)={1=4} (4,10)={1=5}
  12. (5,1)={} (5,2)={1=1} (5,3)={1=1} (5,4)={1=2} (5,5)={1=2} (5,6)={1=3} (5,7)={1=3} (5,8)={1=4} (5,9)={1=4} (5,10)={1=5}
  13. goods:
  14. key=1, value=5

3.2 优化求解背包最大价值与选取物品列表

当前物品i的重量weight[i]小于等于背包承重j时,对状态转移方程进行分析,可以发现:

dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j - weight[i]] + value[i], dp[i-1][j - 2*weight[i]] + 2*value[i] ,......, dp[i-1][j - k*weight[i]] + k*value[i])     

dp[i][j-weight[i]] = Math.max(dp[i-1][j - weight[i]], dp[i-1][j - 2*weight[i]] + value[i], dp[i-1][j - 3*weight[i]] + 2*value[i], ......, dp[i-1][j - k*weight[i]] + (k-1)*value[i])     

其中,k满足条件: 0 <= k <= j/weight[i]。

对比分析以上两个公式,可以推导出新的状态转移方程:

dp[i][j] = Math.max(dp[i-1][j],dp[i][j-weight[i]] + value[i])

据此,可以对朴素求解完全背包算法进行优化,具体实现为:

  1. package com.test.packalgorithm;
  2. import com.google.common.collect.Maps;
  3. import org.apache.commons.collections4.map.MultiKeyMap;
  4. import java.util.Map;
  5. import java.util.Objects;
  6. /**
  7. * 多重背包
  8. */
  9. public class CompletePackRecordOpt {
  10. /**
  11. * 获取最大价值
  12. *
  13. * @param N 物品种类数
  14. * @param W 背包最大承重
  15. * @param weight 物品重量数组
  16. * @param value 物品价值数组
  17. * @param ij2Goods 选择的商品列表
  18. * @return 最大价值
  19. */
  20. public int[][] getDp(int N, int W, int[] weight, int[] value, MultiKeyMap<Integer, Map<Integer, Integer>> ij2Goods) {
  21. // 定义一个数组dp[i][j] i表示当前物品的序号, j表示当前书包的重量
  22. int[][] dp = new int[N + 1][W + 1]; // 【物品种类, 背包容量】
  23. for (int j = 0; j <= W; j++) { // 物品不存在时,最大价值肯定是0
  24. dp[0][j] = 0;
  25. }
  26. for (int i = 1; i <= N; i++) { // 背包重量为0时,最大价值肯定是0
  27. dp[i][0] = 0;
  28. }
  29. for (int i = 1; i <= N; i++) { // 从第1类物品开始选
  30. for (int j = 1; j <= W; j++) {
  31. // 初始化 dp[i][j]
  32. dp[i][j] = dp[i - 1][j];
  33. Map<Integer, Integer> preGoods = ij2Goods.get(i - 1, j);
  34. if (Objects.isNull(preGoods)) {
  35. preGoods = Maps.newHashMap();
  36. }
  37. if (weight[i] <= j) { // 第i类物品重量 小于等于 当前承载重量,根据价值大小判断是否放入。
  38. dp[i][j] = Math.max(dp[i][j - weight[i]] + value[i], dp[i][j]);
  39. if (dp[i][j] > dp[i - 1][j]) {
  40. int maxNumber = j / weight[i]; // 考虑物品的件数限制
  41. int k;
  42. for (k = 0; k <= maxNumber; k++) {
  43. int ijkValue = dp[i - 1][j - (k * weight[i])] + (k * value[i]);
  44. if (dp[i][j] == ijkValue) {
  45. break;
  46. }
  47. }
  48. preGoods = ij2Goods.get(i - 1, j - (k * weight[i]));
  49. if (Objects.isNull(preGoods)) {
  50. preGoods = Maps.newHashMap();
  51. }
  52. Map<Integer, Integer> goods = Maps.newHashMap();
  53. goods.putAll(preGoods);
  54. goods.put(i, k);
  55. ij2Goods.put(i, j, goods);
  56. } else {
  57. ij2Goods.put(i, j, preGoods);
  58. }
  59. } else { // 第i件物品重量大于当前承载重量,则不放入。
  60. ij2Goods.put(i, j, preGoods);
  61. }
  62. }
  63. }
  64. return dp;
  65. }
  66. public static void main(String[] args) {
  67. int N = 5; // 商品种类数
  68. int W = 10; // 背包最大承载重量
  69. int[] w = new int[N + 1]; // 每件物品的重量,为方便理解,下标从1开始
  70. w[1] = 2;
  71. w[2] = 2;
  72. w[3] = 6;
  73. w[4] = 5;
  74. w[5] = 4;
  75. int[] v = new int[N + 1]; // 每件物品的价值
  76. v[1] = 6;
  77. v[2] = 3;
  78. v[3] = 5;
  79. v[4] = 4;
  80. v[5] = 6;
  81. MultiKeyMap<Integer, Map<Integer, Integer>> ij2Goods = new MultiKeyMap<>();
  82. CompletePackRecordOpt obj = new CompletePackRecordOpt();
  83. int[][] dp = obj.getDp(N, W, w, v, ij2Goods);
  84. for (int i = 0; i <= N; i++) {
  85. for (int j = 0; j <= W; j++) {
  86. System.out.printf("(%d,%d)=%-5d", i, j, dp[i][j]);
  87. }
  88. System.out.println();
  89. }
  90. // 背包能够装入物品的最大值为
  91. int maxValue = dp[N][W];
  92. System.out.printf("maxValue=%d", maxValue);
  93. System.out.println();
  94. for (int i = 1; i <= N; i++) {
  95. for (int j = 1; j <= W; j++) {
  96. System.out.printf("(%d,%d)=%-8s", i, j, ij2Goods.get(i, j).toString());
  97. }
  98. System.out.println();
  99. }
  100. System.out.println("goods:");
  101. ij2Goods.get(N, W).forEach((key, value) -> System.out.printf("key=%d, value=%d\n", key, value));
  102. }
  103. }

运行结果为:

  1. (0,0)=0 (0,1)=0 (0,2)=0 (0,3)=0 (0,4)=0 (0,5)=0 (0,6)=0 (0,7)=0 (0,8)=0 (0,9)=0 (0,10)=0
  2. (1,0)=0 (1,1)=0 (1,2)=6 (1,3)=6 (1,4)=12 (1,5)=12 (1,6)=18 (1,7)=18 (1,8)=24 (1,9)=24 (1,10)=30
  3. (2,0)=0 (2,1)=0 (2,2)=6 (2,3)=6 (2,4)=12 (2,5)=12 (2,6)=18 (2,7)=18 (2,8)=24 (2,9)=24 (2,10)=30
  4. (3,0)=0 (3,1)=0 (3,2)=6 (3,3)=6 (3,4)=12 (3,5)=12 (3,6)=18 (3,7)=18 (3,8)=24 (3,9)=24 (3,10)=30
  5. (4,0)=0 (4,1)=0 (4,2)=6 (4,3)=6 (4,4)=12 (4,5)=12 (4,6)=18 (4,7)=18 (4,8)=24 (4,9)=24 (4,10)=30
  6. (5,0)=0 (5,1)=0 (5,2)=6 (5,3)=6 (5,4)=12 (5,5)=12 (5,6)=18 (5,7)=18 (5,8)=24 (5,9)=24 (5,10)=30
  7. maxValue=30
  8. (1,1)={} (1,2)={1=1} (1,3)={1=1} (1,4)={1=2} (1,5)={1=2} (1,6)={1=3} (1,7)={1=3} (1,8)={1=4} (1,9)={1=4} (1,10)={1=5}
  9. (2,1)={} (2,2)={1=1} (2,3)={1=1} (2,4)={1=2} (2,5)={1=2} (2,6)={1=3} (2,7)={1=3} (2,8)={1=4} (2,9)={1=4} (2,10)={1=5}
  10. (3,1)={} (3,2)={1=1} (3,3)={1=1} (3,4)={1=2} (3,5)={1=2} (3,6)={1=3} (3,7)={1=3} (3,8)={1=4} (3,9)={1=4} (3,10)={1=5}
  11. (4,1)={} (4,2)={1=1} (4,3)={1=1} (4,4)={1=2} (4,5)={1=2} (4,6)={1=3} (4,7)={1=3} (4,8)={1=4} (4,9)={1=4} (4,10)={1=5}
  12. (5,1)={} (5,2)={1=1} (5,3)={1=1} (5,4)={1=2} (5,5)={1=2} (5,6)={1=3} (5,7)={1=3} (5,8)={1=4} (5,9)={1=4} (5,10)={1=5}
  13. goods:
  14. key=1, value=5


四、总结

完全背包状态转移方程与多重背包状态转移方程几乎一样,差别只在于物品件数没有限制。在理解0-1背包和多重背包后,理解完全背包就很简单了。


 

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

闽ICP备14008679号