java完全背包,一次性解决三种背包问题

前言

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

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

01背包

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

f7c87fa47164f5b875c6d4fb415eb9f1.png

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

0e751e7e21ac9feb153a761d678789bd.png

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

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

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

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

468245119b12b434293fcb10413557e6.png

从上图可以看出,对于每个物品,我们都有两种选择:选/不选,这就产生了一共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

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

894d77358987f1f1388f9f29ebf0da60.png

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背包”的扩展,其本质区别是物品可以取多少个,只要理清楚了这个区别,这三种背包问题也就不是事了。

987493eca69e6bc12902287a301f7611.png

b739ec46bb5c46d9c0aa4ce35ba1ea56.png

关于找一找教程网

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值