java完全背包,一次性解决三种背包问题
前言
首先,大概讲一下什么是“背包”问题:背包问题是指你有一个容量为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完全背包,一次性解决三种背包问题相关推荐
- java中实现多线程的三种方式
java中实现多线程的三种方式 1.实现多线程的方法: 在java中实现多线程的两途径:继承Thread类,实现Runable接口(Callable) 2.继承Thread类实现多线程: 继承类T ...
- 转list_你知道Java数组转List的三种方式及对比吗?
前言: 本文介绍Java中数组转为List三种情况的优劣对比,以及应用场景的对比,以及程序员常犯的类型转换错误原因解析. 一.最常见方式(未必最佳) 通过 Arrays.asList(strArray ...
- Java 数组转 List 的三种方式及对比
来源 | blog.csdn.net/x541211190/article/details/79597236 本文介绍Java中数组转为List三种情况的优劣对比,以及应用场景的对比,以及程序员常犯的 ...
- 【零基础学Java】—this关键字的三种用法+Java继承的三个特点(二十一)
[零基础学Java]-this关键字的三种用法+Java继承的三个特点(二十一) 一.this关键字的三种用法 在本类的成员方法中,访问本类的成员变量 在本类的成员方法中,访问本类的另一个成员方法 在 ...
- Java中List集合的三种遍历方式(全网最详)
Map集合:链接: Map集合的五种遍历方式及Treemap方法 Set集合:链接: Java中遍历Set集合的三种方法 TreeSet集合:链接: Java深入了解TreeSet,和迭代器遍历方法 ...
- java循环控制语句,简述Java流程控制语句中的三种循环控制语句,并描述下它们的区别。...
简述Java流程控制语句中的三种循环控制语句,并描述下它们的区别. 答:for语句,构建确定循环次数的循环结构 while语句,通常构建不确定循环次数的循环结构 do-while语句,通常构建不确定循 ...
- java 获取当前时间的三种方法是什么
本文主要介绍java获取当前时间的三种方法.这篇文章很详细,有一定的参考价值.有需要的朋友可以参考一下. 总结一些java中获取当前时间的方法. System.currentTimeMillis() ...
- Java中实现接口的三种方式您造吗?
本文介绍了Java中实现接口的三种方式:常规实现方式.匿名内部类和 Lambda表达式实现方式.希望已经了解的同学可以重新温习一下,不了解的同学则从中受益! Java中接口最常规的实现方式 同学们都会 ...
- java线程的任务的三种创建方式,严格地说,就一种,就是如何重写Runnable接口的run()方法
仅简单演示Java中线程任务的三种创建方式 第一种:使用了适配器模式,有返回值,能抛出异常,任务代码写在Callable.call()方法中,Runnable的run方法会通过适配器调用到Callab ...
最新文章
- LIVE 预告 | 华为诺亚韩凯:Transformer in Transformer
- LintCode: Max Tree
- BindingException: Invalid bound statement (not found)问题排查:SpringBoot集成Mybatis重点分析...
- python 中使用ElementTree操作XML
- 一名即将大三的小伙子在疫情期间的思考与总结
- 我是一个*** (三)
- python库封装_使用SIP对C库进行Python封装
- 获取SQL Server数据库增长和收缩事件的详细信息
- hadoop编程-maven环境搭建
- Xamarin只言片语1——Xamarin下的弹框
- JS控制文本框内键盘上下左右键的焦点
- java突击面试章程
- java基本数据类型范围
- execution表达式里写多个条件
- SyncToy 文件同步工具的定时同步方案(不使用第三方软件)
- 配眼镜走过的那些坑。
- 搬运+机翻 Unity插件 OBICloth插件官方CharDemo分析
- 将Opera默认搜索引擎改为百度
- Digital Vision Phoenix 2019(凤凰电影修复软件)官方正式版V2019.1 R2 | 数字电影修复软件下载 | 含Digital Vision Phoenix安装教程
- LTE测量文件MRO,MRE.MRS的区别解释
热门文章
- iText in Action 2nd3.1节(Introducing the concept of direct content)读书笔记
- 【Java】基于Pdfbox解析PDF文档中指定位置的文字和图片
- Python自然语言处理(1):意义与同义词
- 2023中南民族大学计算机考研信息汇总
- PowerBasic版上海期货交易接口技术文档
- “史上最全”Spring Boot详解!java程序员细节到极致的一次,魔鬼
- JDBC连接SQLServer2012报错:驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接。
- 畅销25年,销售百万册,这本神书和背后的软件大神
- 首批 5G 网络就位之后,下一步在何方?
- 案例解读丨基于 SDAF 闭环的消费金融复贷场景精细化运营实践