java多重背包算法,【动态规划】多重背包问题

说明

前面已经介绍完了01背包和完全背包,今天介绍最后一种背包问题——多重背包。

这个背包,听起来就很麻烦的样子。别慌,只要你理解了前面的两种背包问题,拿下多重背包简直小菜一碟。

如果没有看过前两篇01背包和完全背包的文章,强烈建议先阅读一下,因为本文跟前两篇文章关联性很强。

多重背包

有N种物品和一个容量为T的背包,第i种物品最多有M[i]件可用,价值为P[i],体积为V[i],求解:选哪些物品放入背包,可以使得这些物品的价值最大,并且体积总和不超过背包容量。

对比一下完全背包,其实只是多了一个限制条件,完全背包问题中,物品可以选择任意多件,只要你装得下,装多少件都行。

AAffA0nNPuCLAAAAAElFTkSuQmCC

但多重背包就不一样了,每种物品都有指定的数量限制,所以不是你想装,就能一直装的。

举个栗子:有A、B、C三种物品,相应的数量、价格和占用空间如下图:

AAffA0nNPuCLAAAAAElFTkSuQmCC

跟完全背包一样,贪心算法在这里也不适用,我就不重复说明了,大家可以回到上一篇中看看说明。

递归法

还是用之前的套路,我们先来用递归把这个问题解决一次。

用ks(i,t)表示前i种物品放入一个容量为t的背包获得的最大价值,那么对于第i种物品,我们有k种选择,0 <= k <= M[i] && 0 <= k * V[i] <= t,即可以选择0、1、2…M[i]个第i种物品,所以递推表达式为:

ks(i,t) = max{ks(i-1, t - V[i] * k) + P[i] * k}; (0 <= k <= M[i] && 0 <= k * V[i] <= t)

同时,ks(0,t)=0;ks(i,0)=0;

对比一下完全背包的递推关系式:

ks(i,t) = max{ks(i-1, t - V[i] * k) + P[i] * k}; (0 <= k * V[i] <= t)

简直一毛一样,只是k多了一个限制条件而已。

使用上面的栗子,我们可以先写出递归解法:

public static class MultiKnapsack {

private static int[] P={0,2,3,4};

private static int[] V={0,3,4,5};

private static int[] M={0,4,3,2};

private static int T = 15;

@Test

public void soleve1() {

int result = ks(P.length - 1,T);

System.out.println("最大价值为:" + result);

}

private int ks(int i, int t){

int result = 0;

if (i == 0 || t == 0){

// 初始条件

result = 0;

} else if(V[i] > t){

// 装不下该珠宝

result = ks(i-1, t);

} else {

// 可以装下

// 取k个物品i,取其中使得总价值最大的k

for (int k = 0; k <= M[i] && k * V[i] <= t; k++){

int tmp2 = ks(i-1, t - V[i] * k) + P[i] * k;

if (tmp2 > result){

result = tmp2;

}

}

}

return result;

}

}

同样,这里的数组P/V/M分别添加了一个元素0,是为了减少越界判断而做的简单处理,运行如下:

最大价值为:11

对比一下完全背包中的递归解法:

private int ks(int i, int t){

int result = 0;

if (i == 0 || t == 0){

// 初始条件

result = 0;

} else if(V[i] > t){

// 装不下该珠宝

result = ks(i-1, t);

} else {

// 可以装下

// 取k个物品i,取其中使得总价值最大的k

for (int k = 0; k * V[i] <= t; k++){

int tmp2 = ks(i-1, t - V[i] * k) + P[i] * k;

if (tmp2 > result){

result = tmp2;

}

}

}

return result;

}

仅仅多了一个判断条件而已,所以只要弄懂了完全背包,多重背包就不值一提了。

最优化原理和无后效性的证明跟多重背包基本一致,所以就不重复证明了。

动态规划

参考完全背包的动态规划解法,就很容易写出多重背包的动态规划解法。

自上而下记忆法

ks(i,t) = max{ks(i-1, t - V[i] * k) + P[i] * k}; (0 <= k <= M[i] && 0 <= k * V[i] <= t)

public static class MultiKnapsack {

private static int[] P={0,2,3,4};

private static int[] V={0,3,4,5};

private static int[] M={0,4,3,2};

private static int T = 15;

private Integer[][] results = new Integer[P.length + 1][T + 1];

@Test

public void solve2() {

int result = ks2(P.length - 1,T);

System.out.println("最大价值为:" + result);

}

private int ks2(int i, int t){

// 如果该结果已经被计算,那么直接返回

if (results[i][t] != null) return results[i][t];

int result = 0;

if (i == 0 || t == 0){

// 初始条件

result = 0;

} else if(V[i] > t){

// 装不下该珠宝

result = ks2(i-1, t);

} else {

// 可以装下

// 取k个物品,取其中使得价值最大的

for (int k = 0; k <= M[i] && k * V[i] <= t; k++){

int tmp2 = ks2(i-1, t - V[i] * k) + P[i] * k;

if (tmp2 > result){

result = tmp2;

}

}

}

results[i][t] = result;

return result;

}

}

这里其实只是照葫芦画瓢。

自下而上填表法

同样也可以使用填表法来解决,此时需要将数组P、V、M额外添加的元素0去掉。

除了k的限制不一样之外,其他地方跟完全背包的解法完全一致:

public static class MultiKnapsack {

private static int[] P={2,3,4};

private static int[] V={3,4,5};

private static int[] M={4,3,2};

private static int T = 15;

private int[][] dp = new int[P.length + 1][T + 1];

@Test

public void solve3() {

for (int i = 0; i < P.length; i++){

for (int j = 0; j <= T; j++){

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

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

}

}

}

System.out.println("最大价值为:" + dp[P.length][T]);

}

}

跟01背包问题一样,完全背包的空间复杂度也可以进行优化,具体思路这里就不重复介绍了,可以翻看前面的01背包问题优化篇。

优化后的状态转移方程为:

ks(t) = max{ks(t), ks(t - Vi) + Pi}

public static class MultiKnapsack {

private static int[] P={2,3,4};

private static int[] V={3,4,5};

private static int[] M={4,3,2};

private static int T = 15;

private int[] newResults = new int[T + 1];

@Test

public void resolve4() {

int result = ksp(P.length,T);

System.out.println(result);

}

private int ksp(int i, int t){

// 开始填表

for (int m = 0; m < i; m++){

// 考虑第m个物品

// 分两种情况

// 1: M[m] * V[m] > T 则可以当做完全背包问题来处理

if (M[m] * V[m] >= T) {

for (int n = V[m]; n <= t ; n++) {

newResults[n] = Math.max(newResults[n], newResults[n - V[m]] + P[m]);

}

} else {

// 2: M[m] * V[m] < T 则需要在 newResults[n-V[m]*k] + P[m] * k 中找到最大值,0 <= k <= M[m]

for (int n = V[m]; n <= t ; n++) {

int k = 1;

while (k < M[m] && n > V[m] * k ){

newResults[n] = Math.max(newResults[n], newResults[n - V[m] * k] + P[m] * k);

k++;

}

}

}

// 可以在这里输出中间结果

System.out.println(JSON.toJSONString(newResults));

}

return newResults[newResults.length - 1];

}

}

输出如下:

[0,0,0,0,2,2,2,4,4,4,6,6,6,8,8,8]

[0,0,0,0,2,3,3,4,5,6,6,7,8,9,9,10]

[0,0,0,0,2,3,4,4,5,6,7,8,8,9,10,11]

11

这里有一个较大的不同点,在第二层循环中,需要分两种情况考虑,如果 M[m] * V[m] >= T ,那么第m个物品就可以当做完全背包问题来考虑,而如果 M[m] * V[m] < T,则每次选择时,需要从 newResults[n-V[m]*k] + P[m] * k(0 <= k <= M[m])中找到最大值。

代码很简单,但要理解却并不容易,为了加深理解,再画一张图:

AAffA0nNPuCLAAAAAElFTkSuQmCC

多重背包问题同样也可以转化成01背包问题来求解,因为第i件物品最多选 M[i] 件,于是可以把第i种物品转化为M[i]件体积和价值相同的物品,然后再来求解这个01背包问题。

总结

多重背包问题跟完全背包简直如出一辙,仅仅是比完全背包多一个限制条件而已,如果你回过头去看看前一篇文章,就会发现这篇文章简直就是抄袭。。

AAffA0nNPuCLAAAAAElFTkSuQmCC

关于多重背包问题的解析到此就结束了,三个经典的背包问题到这里就告一段落了。

AAffA0nNPuCLAAAAAElFTkSuQmCC

如果有疑问或者有什么想法,也欢迎关注我进行留言交流:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
多重背包问题是指在一定的背包容量下,有多个物品的重量和价值,每个物品有一定的数量限制,要求在放入背包时,使得背包中物品的总价值最大化。动态规划是解决多重背包问题的一种常见方法。 其中,引用提供了一种将多重背包问题转化为01背包问题的方法。这种方法将每个多重背包问题的物品按照其数量进行拆分,使其变成多个01背包问题。然后使用动态规划的思想,依次求解每个01背包问题,最终得到多重背包问题的最优解。 引用提供了一种对完全背包问题的暴力解法做简单修改的方法。该方法同样使用动态规划的思想,对每个物品进行遍历,并在背包承重为j的前提下,求解每个物品能放入的最大数量。然后根据物品的重量和价值进行计算,得到多重背包问题的最优解。 这两种方法都可以用来解决多重背包问题,并根据实际情况选择合适的方法进行求解。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [动态规划(五):多重背包问题](https://blog.csdn.net/qq_42138662/article/details/118434151)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [动态规划多重背包问题](https://blog.csdn.net/qq_42174306/article/details/124345411)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [动态规划4:多重背包](https://blog.csdn.net/qq_40772692/article/details/81435230)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值