前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【动态规划】多重背包问题

【动态规划】多重背包问题

作者头像
弗兰克的猫
发布于 2019-05-25 07:54:39
发布于 2019-05-25 07:54:39
1.3K00
代码可运行
举报
文章被收录于专栏:Java爬坑系列Java爬坑系列
运行总次数:0
代码可运行

说明

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

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

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

多重背包

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

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

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

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

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

递归法

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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;

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ks(i,t) = max{ks(i-1, t - V[i] * k) + P[i] * k};0 <= k * V[i] <= t)

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,是为了减少越界判断而做的简单处理,运行如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
最大价值为:11

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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;
}

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

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

动态规划

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

自上而下记忆法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ks(i,t) = max{ks(i-1, t - V[i] * k) + P[i] * k};0 <= k <= M[i] && 0 <= k * V[i] <= t)
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的限制不一样之外,其他地方跟完全背包的解法完全一致:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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背包问题优化篇。

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ks(t) = max{ks(t), ks(t - Vi) + Pi}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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];
    }
}

输出如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[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])中找到最大值。

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

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

总结

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-05-05 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【动态规划】01背包问题
前面用动态规划解决了正则表达式的问题,感觉还是不过瘾,总觉得对于动态规划的理解还没有到位,所以趁热打铁,继续研究几个动态规划的经典问题,希望能够借此加深对动态规划的理解。在此之前,还需要说两个跟动态规划有关的理论知识。
弗兰克的猫
2019/05/25
1K0
动态规划:关于多重背包,你该了解这些!
之前我们已经体统的讲解了01背包和完全背包,如果没有看过的录友,建议先把如下三篇文章仔细阅读一波。
代码随想录
2021/02/26
3190
【动态规划】一次搞定三种背包问题
看完前面四篇关于背包问题的文章,你会发现背包问题其实也不过如此,而且它们之间有很多相似的地方,本篇文章就来揭开它们面纱,将背包问题彻底搞定。
弗兰克的猫
2019/05/25
1.4K0
细谈多重背包问题
多重背包问题是背包问题的一种扩展,与0/1背包问题和分数背包问题有些不同。在多重背包问题中,每种物品都有限定的数量,不再是仅有一个,而是有多个。问题的描述如下: 给定一个背包容量为C,有n种物品,每种物品有重量w[i]、价值v[i]和数量s[i]。目标是选择物品放入背包,使得它们的总重量不超过背包容量,并且总价值最大。 这个问题在一些应用中更为真实,例如购物时某种商品有多件可供选择。解决多重背包问题的方法通常是在0/1背包问题的基础上进行一些调整。
摆烂小白敲代码
2024/09/23
1210
细谈多重背包问题
【动态规划】01背包问题
前面用动态规划解决了正则表达式的问题,感觉还是不过瘾,总觉得对于动态规划的理解还没有到位,所以趁热打铁,继续研究几个动态规划的经典问题,希望能够借此加深对动态规划的理解。在此之前,还需要说两个跟动态规划有关的理论知识。
用户1370629
2019/03/14
9100
【动态规划】01背包问题
动态规划——多重背包
多重背包区别于01背包和完全背包的关键是,物品的个数一定。 但它们的状态方程还是一样的,对于多次背包问题,我们可以把他转换成01背包问题,但是要注意优化,因为当数据量比较大的时候,容易费时,即时间复杂度太高,需要进行优化。 我们先把之前的状态方程在· f[i][j]表示从i个物品中选取体积不超过j物品的最大价值。 f[i][j]=max(f[i-1][j],f[i-1][j-v]+w,f[i-1][j-2v]+2w,........,f[i-1][j-kv]+kw),kv<j。 这时读者朋友可能会想可不可以像完全背包那样,进行状态方程的转换。emmm,答案是:不可以的,不信的话可以自己尝试转换一下。 下面我们用01背包的思想去解决该问题,对于i个物品有k个,价值为w;那么我们可不可以把它这样理解:我们把这些物品都看成不一样的,再仔细想一下,这不就变成01背包了吗?但是时间太慢了,我们优化一下。 这里的优化为二进制优化 我们把这k个物品进行分割处理, 分为1,2,4,8,16………。只要保证其和大于k就可以。 为什么空2进制来优化呢,因为可以减少时间复杂度,其他0到k之中的任意一个数都可以由分割的二进制数进行组合而成。 例如:k为25,下面进行分割 1,2,4,8,16.怎么分割的呢? 先是1,那么还剩24 2,22 4,28 8,20 16,4 4,0//剩余的自己组成一个 剩下就是01背包了,注意此时不再有i个物品了,而是变成了转换以后的物品个数。
code-child
2023/05/30
2490
动态规划篇——背包问题
动态规划篇——背包问题 本次我们介绍动态规划篇的背包问题,我们会从下面几个角度来介绍: 背包问题概述 零一背包问题 完全背包问题 多重背包问题 分组背包问题 背包问题概述 背包问题算是很经典的动态规划问题,我们在面试中也经常出现 首先我们给出动态规划的思想: 然后我们简单介绍一下背包问题: /*背包问题*/ 有 N 件物品和一个容量是 V 的背包。 第 i 件物品的体积是 vi,价值是 wi。 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大,输出最大价值。 /*输入格式
秋落雨微凉
2022/11/28
5360
动态规划篇——背包问题
【动态规划/背包问题】特殊的多维费用背包问题
将每个任务看作一个「物品」,完成任务所需要的人数看作「成本」,完成任务得到的利润看作「价值」。
宫水三叶的刷题日记
2021/08/13
1.3K0
动态规划:完全背包、多重背包[通俗易懂]
  完全背包:有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。    
全栈程序员站长
2022/09/17
8430
动态规划:完全背包、多重背包[通俗易懂]
九十、动态规划系列背包问题之多重背包
题目是这样的:来源:https://www.acwing.com/problem/content/4/
润森
2022/08/17
5630
九十、动态规划系列背包问题之多重背包
【动态规划/背包问题】分组背包问题练习篇
由于 LeetCode 没有与「分组背包求最大价值」相关的题目,因此我们使用「分组背包求方案数」来作为练习篇。
宫水三叶的刷题日记
2021/07/22
1.3K0
动态规划专题刷题记录③:背包问题
从上图中可以看出,01背包每次计算i时的状态只用到了i-1的状态,所有可以舍去i这一维,优化成一维dp。
Here_SDUT
2022/06/29
1.8K0
动态规划专题刷题记录③:背包问题
【动态规划/背包问题】站在更高的角度看待一般性的背包问题一维空间优化
本篇我们继续完成与 完全背包 相关的练习题,共三篇。本篇是第二篇,第一篇在 这里。
宫水三叶的刷题日记
2021/05/14
5250
【动态规划/背包问题】树形背包问题
从状态定义我们发现,常规的分组背包问题对物品组的考虑是“线性“的(从前往后考虑每个物品组)。
宫水三叶的刷题日记
2021/09/10
2.4K0
【动态规划/背包问题】树形背包问题
背包问题详解:01背包、完全背包、多重背包「建议收藏」
动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中, 可能会有很多可行解。没一个解都对应于一个值,我们希望找到具有最优值的解。胎动规划算法与分治法类似,其基本思想也是将待求解问题分解为若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适用于动态规划算法求解的问题,经分解得到的子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算很多次。如果我们能保存已解决子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解决的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划算法的基本思路。具体的动态规划算法多种多样,但它们具有相同的填表格式。
全栈程序员站长
2022/07/02
6690
【DP解密多重背包问题】:优化策略与实现
多重背包问题是一个经典的组合优化问题。与标准背包问题不同,在多重背包问题中,每种物品可以选择多个,而不是只选择一次。具体来说,给定一个背包的容量和若干种物品,每种物品有一个重量和价值,目标是最大化在背包中放入的物品总价值,同时不超过背包的容量。
用户11305458
2024/10/09
2180
【DP解密多重背包问题】:优化策略与实现
九十一、动态规划系列背包问题之混合背包
背包系列,是动态规划里一类典型的问题,主要有:01背包,完全背包,多重背包,混合背包,二维费用背包,分组背包,有依赖背包和泛化物品等。也就是常说的背包九讲。
润森
2022/08/17
2120
九十一、动态规划系列背包问题之混合背包
算法修炼之筑基篇——筑基一层中期(解决01背包,完全背包,多重背包)
01背包问题是所有背包问题的基础,之后的问题都可以在此基础之上变化,所以一定要理解清楚。尤其是对待不同问题,找出状态转移方程是解题的关键。
命运之光
2024/03/20
1360
算法修炼之筑基篇——筑基一层中期(解决01背包,完全背包,多重背包)
【动态规划/背包问题】分组背包问题
但对于「组内」物品而言,由于最多只能选一件物品,因此对于成本相同的多件物品,我们应当只保留价值最大的物品,从而让总的物品数量变少。
宫水三叶的刷题日记
2021/07/22
2.1K0
背包九讲——多重背包问题
多重背包问题是背包问题的一种扩展,与0/1背包问题和分数背包问题有些不同。在多重背包问题中,每种物品都有限定的数量,不再是仅有一个,而是有多个。问题的描述如下: 给定一个背包容量为C,有n种物品,每种物品有重量w[i]、价值v[i]和数量s[i]。目标是选择物品放入背包,使得它们的总重量不超过背包容量,并且总价值最大。 这个问题在一些应用中更为真实,例如购物时某种商品有多件可供选择。解决多重背包问题的方法通常是在0/1背包问题的基础上进行一些调整。
摆烂小白敲代码
2024/10/19
2040
背包九讲——多重背包问题
推荐阅读
相关推荐
【动态规划】01背包问题
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验