前言

首先,大概讲一下什么是“背包”问题:背包问题是指你有一个容量为V的背包,然后有n个物品在你面前,你要怎么装才能使得背包里的物品总价值最大。而每种物品是只有1个,还是有多个,亦或是有无限个,这就是“01背包”、“多重背包”、“完全背包”的主要区别。

这里先打断一下,给自己一点时间,先思考一下这样的区别可能会在解法上有什么不同的区别,接着我们就开始往下看每种背包是怎么解决的。

01背包

假设你是一个小偷,在一个夜黑风高的晚上,偷偷摸摸地进了一个富人的别墅里,看到了有下面三件物品,你心想:美汁汁,这次赚大了。

正准备把这些物品全部打包带走,但是很不巧,这时候外面传来了开门的声音,你来不及打包了,只能将这些物品装进袋子赶紧跑路,这是一个空间大小为4磅的背包,问要怎么选才能使这次的收获最大。

先打断一下,肯定有人会疑惑为什么“01背包”要叫“01背包”,而不是“23背包”或者“45”背包???

很简单,顾名思义,每件物品只能选(1)或者不选(0),选就只能选一个,所以为1,不选就直接为0,所以叫做“01”。

看到这个问题,可能有人会说:那还不简单,直接把笔记本电脑和吉他装进去不就好了。的确如此,但是如果物品一多的时候,你就很难再这么轻松地判断出来了,这时候“动态规划”便应运而生。

接下来,我们开始做选择(也就是暴力递归的思想:尝试所有的选择)。

从上图可以看出,对于每个物品,我们都有两种选择:选/不选,这就产生了一共5种选择,最佳的是0,3500这个选择,接下来我们使用函数来表达一般化的情况。

暴力递归解法

我们先将具体问题转为一般情况的问题,假设有n件物品(x1,x2,...,xn),背包大小为C,每件物品的质量为Wi,价值为Vi,xi取0或1,表示第i个物品取或不取。接着按照暴力递归的三要素来写函数

函数的定义:f(i, j)表示背包剩余容量为i的时候,前j个物品最佳组合的价值

递推关系式(即当前调用单元做了什么):对于第j件物品,我们先判断背包剩余容量是否大于当前物品的质量

2.1 如果装不进,就跳过 f(i, j) = f(i,j-1)

2.2 装的进,有选或不选两种情况,取总价值大的,对应着f(i, j) = max(f(i,j-1), Vj + f(i - Wj, j - 1))

递归结束条件:背包剩余容量为0,或者已经遍历完了所有物品

理清楚了上面的三个条件,就很容易写出递归解法了。

public class Solution {

int[] vs = {3000, 2000, 1500}; //物品的价值

int[] ws = {4, 3, 1}; //物品的质量

public int maximumValue() {

int result = maximumValueHelper(4, 2);

return result;

}

private int maximumValueHelper(int i, int j) {

//base case:

if (j < 0 || i == 0) {

return 0;

}

int result = 0;

//判断当前容量能否装进第j件物品

if (i < ws[j]) {

result = maximumValueHelper(i, j - 1);

} else {

//不取第j件物品

int get = maximumValueHelper(i, j - 1);

//取第j件物品

int notGet = vs[j] + maximumValueHelper(i - ws[j], j - 1);

result = Math.max(get, notGet);

}

return result;

}

}

复制代码

带记忆数组的递归

在递归过程中,存在着大量的重复计算,所以可以使用一个“记忆数组”来减少重复计算。

public class Solution{

int[] vs = {3000, 2000, 1500}; //物品的价值

int[] ws = {4, 3, 1}; //物品的质量

//记忆数组:memo[i][j]表示背包剩余容量为i,第0~第j件物品的最佳组合的价值

int[][] memo = new int[5][3];

public int maximumValue() {

int result = maximumValueHelper(4, 2);

Arrays.fill(memo, -1);

return result;

}

private int maximumValueHelper(int i, int j) {

//base case:

if (j < 0 || i == 0) {

return 0;

}

//如果出现过,就直接返回

if (memo[i][j] != -1) {

return memo[i][j];

}

int result = 0;

//判断当前容量能否装进第j件物品

if (i < ws[j]) {

result = maximumValueHelper(i, j - 1);

} else {

//不取第j件物品

int get = maximumValueHelper(i, j - 1);

//取第j件物品

int notGet = vs[j] + maximumValueHelper(i - ws[j], j - 1);

result = Math.max(get, notGet);

}

memo[i][j] = result;

return result;

}

}

复制代码

动态规划

动态规划也是使用一个二维数组来减少重复计算,思路和“带记忆数组的递归”方法类似,不同的 是“动态规划”是自底向上,“带记忆数组的递归”是自顶向下。准备好一个二位数组之后,接下来就是简单的填表过程。

首先第一列dp[0][j],背包空间为0,所以最大价值都是0

接下来其他的格子按照递推式来填

public class Solution {

int[] vs = {3000, 2000, 1500}; //物品的价值

int[] ws = {4, 3, 1}; //物品的质量

public int maximumValue() {

int[][] dp = new int[5][3];

//填充第一行

for (int j = 0; j < dp[0].length; j++) {

dp[0][j] = 0;

}

//接下来的格子按照递推式来填

for (int i = 1; i < dp.length; i++) {

for (int j = 0; j < dp[0].length; j++) {

//如果背包剩余容量小于第j件物品的质量

if (i < ws[j]) {

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

} else {

dp[i][j] = Math.max(dp[i][j - 1], vs[j] + dp[i - ws[j]][j - 1]);

}

}

}

return dp[4][2];

}

}

复制代码

多重背包

“多重背包”和“01背包”的区别就在于“多重背包”种每个物品的数量有多个,所以在选第j件物品的时候,可以选0个,或者1个,或者在背包容量足够的情况下全选。对照着“01背包”的递推式,我们可以写出“多重背包”的递推式f(i, j) = max(k * Vj + f(i - k * Wj, j - 1)) {k * wj <= i && k <= 第j件物品个数}

public class Solution {

int[] vs = {3000, 2000, 1500}; //物品的价值

int[] ws = {3, 2, 1}; //物品的质量

int[] nums = {3, 2, 4}; //对应每件物品的数量

public int maximumValue() {

//背包大小为10

int result = maximumValueHelper(10, 2);

return result;

}

private int maximumValueHelper(int i, int j) {

//base case:

if (j < 0 || i == 0) {

return 0;

}

int result = 0;

//判断当前容量能否装进第j件物品

if (i < ws[j]) {

result = maximumValueHelper(i, j - 1);

} else {

//第j件物品可以取0~nums[j]个

for (int k = 0; k <= nums[j] && k * ws[j] <= 10; k++) {

int tmp = k * vs[j] + maximumValueHelper(i - k * ws[j], j - 1);

result = tmp > result ? tmp : result;

}

}

return result;

}

}

复制代码

带记忆数组的递归仿照“01背包”中的解答可以写出,并没有实质上的改变。

动态规划

public class Solution{

int[] vs = {3000, 2000, 1500}; //物品的价值

int[] ws = {3, 2, 1}; //物品的质量

int[] nums = {3, 2, 4}; //对应物品的个数

public int maximumValue() {

//背包大小为10

int[][] dp = new int[11][3];

//填充第一行

for (int j = 0; j < dp[0].length; j++) {

dp[0][j] = 0;

}

//接下来的格子按照递推式来填

for (int i = 1; i < dp.length; i++) {

for (int j = 0; j < dp[0].length; j++) {

//如果背包剩余容量小于第j件物品的质量

if (i < ws[j]) {

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

} else {

//第j件物品可以取多个

for (int k = 0; k < nums[j] && k * ws[j] <= i; k++) {

dp[i][j] = Math.max(dp[i][j], k * vs[j] + dp[i - k * ws[j]][j - 1]);

}

}

}

}

return dp[10][2];

}

}

复制代码

完全背包

完全背包的特点是每件物品可以取无限次,所以相比于多重背包少了一个约束条件k

public class Solution{

int[] vs = {3000, 2000, 1500}; //物品的价值

int[] ws = {3, 2, 1}; //物品的质量

public int maximumValue() {

//背包大小为10

int[][] dp = new int[11][3];

//填充第一行

for (int j = 0; j < dp[0].length; j++) {

dp[0][j] = 0;

}

//接下来的格子按照递推式来填

for (int i = 1; i < dp.length; i++) {

for (int j = 0; j < dp[0].length; j++) {

//如果背包剩余容量小于第j件物品的质量

if (i < ws[j]) {

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

} else {

//第j件物品可以取多个

for (int k = 0; k * ws[j] <= i; k++) {

dp[i][j] = Math.max(dp[i][j], k * vs[j] + dp[i - k * ws[j]][j - 1]);

}

}

}

}

return dp[10][2];

}

}

复制代码

总结

“完全背包”和“多重背包”是“01背包”的扩展,其本质区别是物品可以取多少个,只要理清楚了这个区别,这三种背包问题也就不是事了。

关于找一找教程网

本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。

本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。

[一次性解决三种背包问题]http://www.zyiz.net/tech/detail-118277.html

java完全背包,一次性解决三种背包问题相关推荐

  1. java中实现多线程的三种方式

    java中实现多线程的三种方式 1.实现多线程的方法: 在java中实现多线程的两途径:继承Thread类,实现Runable接口(Callable) 2.继承Thread类实现多线程: ​ 继承类T ...

  2. 转list_你知道Java数组转List的三种方式及对比吗?

    前言: 本文介绍Java中数组转为List三种情况的优劣对比,以及应用场景的对比,以及程序员常犯的类型转换错误原因解析. 一.最常见方式(未必最佳) 通过 Arrays.asList(strArray ...

  3. Java 数组转 List 的三种方式及对比

    来源 | blog.csdn.net/x541211190/article/details/79597236 本文介绍Java中数组转为List三种情况的优劣对比,以及应用场景的对比,以及程序员常犯的 ...

  4. 【零基础学Java】—this关键字的三种用法+Java继承的三个特点(二十一)

    [零基础学Java]-this关键字的三种用法+Java继承的三个特点(二十一) 一.this关键字的三种用法 在本类的成员方法中,访问本类的成员变量 在本类的成员方法中,访问本类的另一个成员方法 在 ...

  5. Java中List集合的三种遍历方式(全网最详)

    Map集合:链接: Map集合的五种遍历方式及Treemap方法 Set集合:链接: Java中遍历Set集合的三种方法 TreeSet集合:链接: Java深入了解TreeSet,和迭代器遍历方法 ...

  6. java循环控制语句,简述Java流程控制语句中的三种循环控制语句,并描述下它们的区别。...

    简述Java流程控制语句中的三种循环控制语句,并描述下它们的区别. 答:for语句,构建确定循环次数的循环结构 while语句,通常构建不确定循环次数的循环结构 do-while语句,通常构建不确定循 ...

  7. java 获取当前时间的三种方法是什么

    本文主要介绍java获取当前时间的三种方法.这篇文章很详细,有一定的参考价值.有需要的朋友可以参考一下. 总结一些java中获取当前时间的方法. System.currentTimeMillis() ...

  8. Java中实现接口的三种方式您造吗?

    本文介绍了Java中实现接口的三种方式:常规实现方式.匿名内部类和 Lambda表达式实现方式.希望已经了解的同学可以重新温习一下,不了解的同学则从中受益! Java中接口最常规的实现方式 同学们都会 ...

  9. java线程的任务的三种创建方式,严格地说,就一种,就是如何重写Runnable接口的run()方法

    仅简单演示Java中线程任务的三种创建方式 第一种:使用了适配器模式,有返回值,能抛出异常,任务代码写在Callable.call()方法中,Runnable的run方法会通过适配器调用到Callab ...

最新文章

  1. LIVE 预告 | 华为诺亚韩凯:Transformer in Transformer
  2. LintCode: Max Tree
  3. BindingException: Invalid bound statement (not found)问题排查:SpringBoot集成Mybatis重点分析...
  4. python 中使用ElementTree操作XML
  5. 一名即将大三的小伙子在疫情期间的思考与总结
  6. 我是一个*** (三)
  7. python库封装_使用SIP对C库进行Python封装
  8. 获取SQL Server数据库增长和收缩事件的详细信息
  9. hadoop编程-maven环境搭建
  10. Xamarin只言片语1——Xamarin下的弹框
  11. JS控制文本框内键盘上下左右键的焦点
  12. java突击面试章程
  13. java基本数据类型范围
  14. execution表达式里写多个条件
  15. SyncToy 文件同步工具的定时同步方案(不使用第三方软件)
  16. 配眼镜走过的那些坑。
  17. 搬运+机翻 Unity插件 OBICloth插件官方CharDemo分析
  18. 将Opera默认搜索引擎改为百度
  19. Digital Vision Phoenix 2019(凤凰电影修复软件)官方正式版V2019.1 R2 | 数字电影修复软件下载 | 含Digital Vision Phoenix安装教程
  20. LTE测量文件MRO,MRE.MRS的区别解释

热门文章

  1. iText in Action 2nd3.1节(Introducing the concept of direct content)读书笔记
  2. 【Java】基于Pdfbox解析PDF文档中指定位置的文字和图片
  3. Python自然语言处理(1):意义与同义词
  4. 2023中南民族大学计算机考研信息汇总
  5. PowerBasic版上海期货交易接口技术文档
  6. “史上最全”Spring Boot详解!java程序员细节到极致的一次,魔鬼
  7. JDBC连接SQLServer2012报错:驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接。
  8. 畅销25年,销售百万册,这本神书和背后的软件大神
  9. 首批 5G 网络就位之后,下一步在何方?
  10. 案例解读丨基于 SDAF 闭环的消费金融复贷场景精细化运营实践