一、完全背包问题描述

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

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

二、解题思路

我们先看下多重背包实现原理:背包问题之多重背包_爱思考的实践者的博客-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 朴素求解背包最大价值与选取物品列表

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

package com.test.packalgorithm;import com.google.common.collect.Maps;
import org.apache.commons.collections4.map.MultiKeyMap;import java.util.Map;
import java.util.Objects;/*** 多重背包*/
public class CompletePackRecord {/*** 获取最大价值** @param N 物品种类数* @param W 背包最大承重* @param weight 物品重量数组* @param value 物品价值数组* @param ij2Goods 选择的商品列表* @return 最大价值*/public int[][] getDp(int N, int W,  int[] weight, int[] value, MultiKeyMap<Integer, Map<Integer, Integer>> ij2Goods) {// 定义一个数组dp[i][j]  i表示当前物品的种类序号, j表示当前书包的重量int[][] dp = new int[N + 1][W + 1]; // 【物品种类, 背包容量】for (int j = 0; j <= W; j++) {  // 物品不存在时,最大价值肯定是0dp[0][j] = 0;}for (int i = 1; i <= N; i++) {  // 背包重量为0时,最大价值肯定是0dp[i][0] = 0;}for (int i = 1; i <= N; i++) {  // 从第1类物品开始选for (int j = 1; j <= W; j++) {// 初始化 dp[i][j]dp[i][j] = dp[i - 1][j];Map<Integer, Integer> preGoods = ij2Goods.get(i - 1, j);if (Objects.isNull(preGoods)) {preGoods = Maps.newHashMap();}if (weight[i] <= j) { // 第i类物品重量 小于等于 当前承载重量,根据价值大小判断是否放入。// 考虑物品的件数限制, 寻找dp[i][j]的最大值int maxNumber = j / weight[i];for (int k = 0; k <= maxNumber; k++) {int ijkValue = dp[i - 1][j - (k * weight[i])] + (k * value[i]);dp[i][j] = Math.max(dp[i][j], ijkValue);}if (dp[i][j] > dp[i - 1][j]) {int k;for (k = 0; k <= maxNumber; k++) {int ijValue = dp[i - 1][j - (k * weight[i])] + (k * value[i]);if (dp[i][j] == ijValue) {break;}}preGoods = ij2Goods.get(i - 1, j - (k * weight[i]));if (Objects.isNull(preGoods)) {preGoods = Maps.newHashMap();}Map<Integer, Integer> goods = Maps.newHashMap();goods.putAll(preGoods);goods.put(i, k);ij2Goods.put(i, j, goods);} else {ij2Goods.put(i, j, preGoods);}} else { // 第i件物品重量大于当前承载重量,则不放入。ij2Goods.put(i, j, preGoods);}}}return dp;}public static void main(String[] args) {int N = 5; // 商品种类数int W = 10; // 背包最大承载重量int[] w = new int[N + 1]; // 每件物品的重量,为方便理解,下标从1开始w[1] = 2;w[2] = 2;w[3] = 6;w[4] = 5;w[5] = 4;int[] v = new int[N + 1]; // 每件物品的价值v[1] = 6;v[2] = 3;v[3] = 5;v[4] = 4;v[5] = 6;MultiKeyMap<Integer, Map<Integer, Integer>> ij2Goods = new MultiKeyMap<>();CompletePackRecord obj = new CompletePackRecord();int[][] dp = obj.getDp(N, W, w, v, ij2Goods);for (int i = 0; i <= N; i++) {for (int j = 0; j <= W; j++) {System.out.printf("(%d,%d)=%-5d", i, j, dp[i][j]);}System.out.println();}// 背包能够装入物品的最大值为int maxValue = dp[N][W];System.out.printf("maxValue=%d", maxValue);System.out.println();for (int i = 1; i <= N; i++) {for (int j = 1; j <= W; j++) {System.out.printf("(%d,%d)=%-8s", i, j, ij2Goods.get(i, j).toString());}System.out.println();}System.out.println("goods:");ij2Goods.get(N, W).forEach((key, value)-> System.out.printf("key=%d, value=%d\n", key, value));}
}

运行结果为:

(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
(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
(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
(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
(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
(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
maxValue=30
(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}
(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}
(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}
(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}
(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}
goods:
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])

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

package com.test.packalgorithm;import com.google.common.collect.Maps;
import org.apache.commons.collections4.map.MultiKeyMap;import java.util.Map;
import java.util.Objects;/*** 多重背包*/
public class CompletePackRecordOpt {/*** 获取最大价值** @param N 物品种类数* @param W 背包最大承重* @param weight 物品重量数组* @param value 物品价值数组* @param ij2Goods 选择的商品列表* @return 最大价值*/public int[][] getDp(int N, int W, int[] weight, int[] value, MultiKeyMap<Integer, Map<Integer, Integer>> ij2Goods) {// 定义一个数组dp[i][j]  i表示当前物品的序号, j表示当前书包的重量int[][] dp = new int[N + 1][W + 1]; // 【物品种类, 背包容量】for (int j = 0; j <= W; j++) {  // 物品不存在时,最大价值肯定是0dp[0][j] = 0;}for (int i = 1; i <= N; i++) {  // 背包重量为0时,最大价值肯定是0dp[i][0] = 0;}for (int i = 1; i <= N; i++) {  // 从第1类物品开始选for (int j = 1; j <= W; j++) {// 初始化 dp[i][j]dp[i][j] = dp[i - 1][j];Map<Integer, Integer> preGoods = ij2Goods.get(i - 1, j);if (Objects.isNull(preGoods)) {preGoods = Maps.newHashMap();}if (weight[i] <= j) { // 第i类物品重量 小于等于 当前承载重量,根据价值大小判断是否放入。dp[i][j] = Math.max(dp[i][j - weight[i]] + value[i], dp[i][j]);if (dp[i][j] > dp[i - 1][j]) {int maxNumber = j / weight[i];  // 考虑物品的件数限制int k;for (k = 0; k <= maxNumber; k++) {int ijkValue = dp[i - 1][j - (k * weight[i])] + (k * value[i]);if (dp[i][j] == ijkValue) {break;}}preGoods = ij2Goods.get(i - 1, j - (k * weight[i]));if (Objects.isNull(preGoods)) {preGoods = Maps.newHashMap();}Map<Integer, Integer> goods = Maps.newHashMap();goods.putAll(preGoods);goods.put(i, k);ij2Goods.put(i, j, goods);} else {ij2Goods.put(i, j, preGoods);}} else { // 第i件物品重量大于当前承载重量,则不放入。ij2Goods.put(i, j, preGoods);}}}return dp;}public static void main(String[] args) {int N = 5; // 商品种类数int W = 10; // 背包最大承载重量int[] w = new int[N + 1]; // 每件物品的重量,为方便理解,下标从1开始w[1] = 2;w[2] = 2;w[3] = 6;w[4] = 5;w[5] = 4;int[] v = new int[N + 1]; // 每件物品的价值v[1] = 6;v[2] = 3;v[3] = 5;v[4] = 4;v[5] = 6;MultiKeyMap<Integer, Map<Integer, Integer>> ij2Goods = new MultiKeyMap<>();CompletePackRecordOpt obj = new CompletePackRecordOpt();int[][] dp = obj.getDp(N, W, w, v, ij2Goods);for (int i = 0; i <= N; i++) {for (int j = 0; j <= W; j++) {System.out.printf("(%d,%d)=%-5d", i, j, dp[i][j]);}System.out.println();}// 背包能够装入物品的最大值为int maxValue = dp[N][W];System.out.printf("maxValue=%d", maxValue);System.out.println();for (int i = 1; i <= N; i++) {for (int j = 1; j <= W; j++) {System.out.printf("(%d,%d)=%-8s", i, j, ij2Goods.get(i, j).toString());}System.out.println();}System.out.println("goods:");ij2Goods.get(N, W).forEach((key, value) -> System.out.printf("key=%d, value=%d\n", key, value));}
}

运行结果为:

(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
(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
(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
(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
(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
(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
maxValue=30
(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}
(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}
(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}
(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}
(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}
goods:
key=1, value=5

四、总结

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

背包问题之完全背包算法详解相关推荐

  1. 动态规划01背包算法详解

    动态规划算法核心思想: 将大的问题转化为小问题进行解决. 01背包问题: 01背包是在M件物品取出若干件放在空间为W的背包里,每件物品的重量为W1,W2至Wn,与之相对应的价值为V1V2至Vn.01背 ...

  2. 多重背包O(N*V)算法详解(——使用单调队列)

    多重背包O(N*V)算法详解(--使用单调队列) 多重背包问题: 有N种物品和容量为V的背包,若第i种物品,容量为v[i],价值为w[i],共有n[i]件.怎样装才能使背包内的物品总价值最大? 网上关 ...

  3. Matlab人脸检测算法详解

    这是一个Matlab人脸检测算法详解 前言 人脸检测结果 算法详解 源代码解析 所调用函数解析 bwlabel(BW,n) regionprops rectangle 总结 前言 目前主流的人脸检测与 ...

  4. 图论-最短路Dijkstra算法详解超详 有图解

    整体来看dij就是从起点开始扩散致整个图的过程,为什么说他稳定呢,是因为他每次迭代,都能得到至少一个结点的最短路.(不像SPFA,玄学复杂度) 但是他的缺点就是不能处理带负权值的边,和代码量稍稍复杂. ...

  5. C++中的STL算法详解

    1.STL算法详解 STL提供能在各种容器中通用的算法(大约有70种),如插入.删除.查找.排序等.算法就是函数模板,算法通过迭代器来操纵容器中的元素.许多算法操作的是容器上的一个区间(也可以是整个容 ...

  6. 粒子群(pso)算法详解matlab代码,粒子群(pso)算法详解matlab代码

    粒子群(pso)算法详解matlab代码 (1)---- 一.粒子群算法的历史 粒子群算法源于复杂适应系统(Complex Adaptive System,CAS).CAS理论于1994年正式提出,C ...

  7. 基础排序算法详解与优化

    文章图片存储在GitHub,网速不佳的朋友,请看<基础排序算法详解与优化> 或者 来我的技术小站 godbmw.com 1. 谈谈基础排序 常见的基础排序有选择排序.冒泡排序和插入排序.众 ...

  8. 目标检测 RCNN算法详解

    原文:http://blog.csdn.net/shenxiaolu1984/article/details/51066975 [目标检测]RCNN算法详解 Girshick, Ross, et al ...

  9. Twitter-Snowflake,64位自增ID算法详解

    Twitter-Snowflake,64位自增ID算法详解 from: http://www.lanindex.com/twitter-snowflake%EF%BC%8C64%E4%BD%8D%E8 ...

  10. 数据结构与算法详解目录

    数据结构与算法详解是一本以实例和实践为主的图书,主要是经典的数据结构与常见算法案例,来自历年考研.软考等考题,有算法思路和完整的代码,最后提供了C语言调试技术的方法. 后续配套微课视频. 第0章  基 ...

最新文章

  1. 大厂前实习生被威胁,“关闭开源项目,不然就告你”
  2. 虚拟机无法访问主机mysql_解决虚拟机linux端mysql数据库无法远程访问
  3. Java内存模型深度解析:volatile--转
  4. bin/...的访问被拒绝被拒绝的问题
  5. 马云启动“NASA”计划 为未来20年愿景研发核心科技
  6. Object family 在Object search中的default逻辑
  7. 从Banner入手保护linux系统服务器
  8. 项目管理——文档的重要性
  9. 社会计算机比赛,哈尔滨工业大学社会计算与信息检索研究中心 – 理解语言,认知社会 » IR-Lab参加计算机学院“光熙杯”篮球赛...
  10. python操作redis集群_python 连接管理作redis集群
  11. python调用robotframework api_python+robot framework接口自动化测试
  12. Ansible11:变量详解
  13. java 选择排序入门
  14. MCP2515波特率配置
  15. git(4)服务器上的 Git
  16. MacOS打开多个微信的方法(代码+脚本)
  17. Linux应急响应排查
  18. 用flex做的3D坦克游戏
  19. H.264中NALU、RBSP、SODB的关系 (弄清码流结构)
  20. 写作业用白光还是暖白光?分享色温4000K暖白光的护眼台灯

热门文章

  1. Django之Model数据库数据的操作
  2. 联想台式电脑序列号查看方法
  3. 微信小程序 下拉刷新 性能优化 参考饿了么小程序首页列表加载
  4. 为什么提倡面向接口编程
  5. LQR 的直观推导及简单应用
  6. 电脑的ppt打不开计算机二级,ppt打不开怎么办?详细教您详细解决方法
  7. 威富通移动支付开发文档
  8. Git和SVN的区别(以后别再问我了)
  9. 支付宝芝麻信用分申请
  10. NPDP国际产品经理认证是什么?看完你就懂了