动态规划之0-1背包问题

问题描述:

    现有n件物品和一个容量为c的背包。第i件物品的重量是重量为w[i],价值是v[i]。已知对于一件物品必须选择取(用1表示)或者不取(用0表示),且每件物品只能被取一次(这就是“0-1”的含义)。求放置哪些物品进背包,可使这些物品的重量总和不超过背包容量,且价值总和最大。

求解思路:

    假设有5件物品,其重量分别是w={2,2,6,5,4},价值分别是v={6,3,5,4,6},背包容量为10。在数学问题中这是典型的线性规划问题,我们可以在线性约束范围内求解目标表达式。但是怎么用计算机语言实现呢?我们可以先这样考虑,当背包容量为1时,如何放置物品才能使背包中价值最大;同样当背包容量为2时,如何放置能使背包中价值最大,以此类推,直到背包容量为10。此时我们需要维护一张二维表m[i][j],其中横坐标i表示物品,纵坐标表示背包容量(1<=j<=10)。
                                                                   0-1背包问题的递推二维表

    m[i][j]表示当可以放入前i件物品且背包容量为j时的最大价值。当只能放入第一件物品即i=0时:若背包容量j<w[0],物品不能够被放入背包;若j>=w[0]时,物品可以放入背包,此时m[0][j]=v[0]。当可以放入前2件物品即i=1时,我们需要进行这样的处理:若j<w[1]时,说明第2件物品不能被放入背包内,此时背包的最大价值为背包中只放入第一件物品的最大价值,即m[1][j]=m[0][j];若j>=w[1]时,假设此时背包容量j=8,第二件物品可以被放入背包内,那么便会出现两种情况:

    (1)将第二件物品放入背包,那么背包中物品的最大价值是多少呢?因为第二件物品重量为w[1]=2,在将第二件物品放入背包之前,背包的容量应为j-w[1]=8-2=6,此时背包的最大价值是m[0][6],因此若将第二件物品放入背包,其背包的最大价值m[1][j]=m[0][j-w[1]]+v[1];

    (2)不将第二件物品放入背包,那么此时背包中物品的最大价值依然为只放入第一件物品时背包的最大价值,即m[1][j]=m[0][j];

    我们选取(1)(2)中价值的较大者作为i=1,j=8时背包中的最大价值。

    i=2,3,4时的分析同上,直到背包的容量为10,此时m[4][10]即为背包中物品的最大价值。

    有了上面的分析,我们很容易写出下面的递归关系:

    (1)i=0  当j<w[0]时,m[0][j]=0;当j>=w[0]时,m[0][j]=v[0]。

    (2)i>0  当j<w[i],m[i][j]=m[i-1][j];当j>=w[i],m[i][j]=max{m[i-1][j-w[i]]+v[i],m[i-1][j]}。

    得到了满足约束条件的背包中物品的最大价值后,需要知道是哪些物品被放入了背包。观察二维表m[i][j],我们注意到m[i][c]表示当背包重量为题目中要求的c时背包的最大价值,那么在得到m[i][c]之前,我们必然是比较了m[i-1][j-w[i]]+v[i]与m[i-1][j]的大小,从而决定是否将物品放入背包。所以我们可以利用回溯的方法,若m[i][j]=m[i-1][j],那么物品没有放入背包;否则物品一定被放入背包。因此我们可以从最后一件物品开始,一步一步回退到第一件物品,直到找到所有的物品放入背包的情况。本题中物品的装入情况如表中红色和蓝色部分所示,其中红色表示当前物品被装入背包,蓝色表示没有装入背包。
代码实现:
public class Main {
    public static void main(String[] args) {
        int []w={2,2,6,5,4}; //物品重量
        int []v={6,3,5,4,6}; //物品价值
        int c=10;            //背包容量
        int []x=new int[5];  //记录物品装入情况,0表示不转入,1表示装入
        x[0]=1; //初始值表示第一个物品已装入背包
        int [][]m=new int[5][c+1];//需要维护的二维表,为了方便计算加入一列,其中第0列表示背包容量为0时背包的最大价值为0
        /*
        * 初始化第一行,即背包中装入第一件物品
        * */
        for(int j=1;j<=c;j++){
            if(j>=w[0]){
                m[0][j]=v[0];
            }
        }
        /*
        * 背包中依次装入其他的物品
        * */
        for(int i=1;i<5;i++){
            for(int j=1;j<=c;j++){
                if(j<w[i])m[i][j]=m[i-1][j]; //不装入背包
                else{
                    if(m[i-1][j-w[i]]+v[i]>m[i-1][j]) m[i][j]=m[i-1][j-w[i]]+v[i]; //选择价值较大者
                    else m[i][j]=m[i-1][j];
                }
            }
        }
        System.out.println("背包的最大价值为:"+m[w.length-1][c]);
        for(int i=4;i>=1;i--){
            if(m[i][c]>m[i-1][c]){
                x[i]=1; //装入背包
                c-=w[i]; //物品i装入背包之前背包的容量
            }
            else x[i]=0; //没有装入背包
        }
        System.out.print("装入背包的物品编号是:");
        for(int i=0;i<5;i++){
            if(x[i]==1) System.out.printf("%2d",(i+1));
        }
    }
}

全部评论
不错呦!
点赞
送花
回复
分享
发布于 2016-01-11 15:23
第7行可以不要 在第37行前,加入代码:if (c != 0) x[0] = m[0][c] != 0 ? 1 : 0; 文章写得很好,对于理解背包算法很有帮助。希望作者,能更新一下文章小bug
点赞
送花
回复
分享
发布于 2018-11-27 15:59
滴滴
校招火热招聘中
官网直投
不错呦!
点赞
送花
回复
分享
发布于 2016-01-10 10:18
zan
点赞
送花
回复
分享
发布于 2016-01-13 21:06
这样不是默认第一个物体加入package里面了吗?把物体的序号颠倒一下就会有bug
点赞
送花
回复
分享
发布于 2016-04-10 23:22
不错不错,Nice
点赞
送花
回复
分享
发布于 2018-10-03 23:20
写的很好,有助于理解背包问题
点赞
送花
回复
分享
发布于 2019-01-23 16:44
如果有多种放入方式,最后都能得到最大价值呢。 文章很好,前边对于得到最大价值,理解背包算法很有帮助,但是获取放入方式,总感觉不妥。
点赞
送花
回复
分享
发布于 2019-12-12 11:11
哇,必须夸一夸,太赞了,看完顿悟,马上感觉我会了😁😁😁,写的太好了
点赞
送花
回复
分享
发布于 2020-01-04 10:48

相关推荐

头像
04-11 19:06
已编辑
门头沟学院 材料类
包含了腾讯一二面,搜狐一面,雷火一二面,快手(游戏图形学)一二面等等内容,只记录一部分有意思的问题,图形学八股和cpp八股不在此处。我自己项目做的比较细,我不是实现了一个引擎而是实现了一些图形算法优化,这导致面试官都会对优化细节,实现细节细细拷问,同时夹杂一些面试官自己的思考(你是这么实现的,但我觉得你这样会有blabla问题&nbsp;or&nbsp;你是这么实现的,你遇到某某问题怎么办,能处理吗能优化吗)。延迟管线中需要处理复杂材质和光照模型怎么办?比如这一部分物体是某种shading&nbsp;model而另一部分物体是另一种model?csm如何处理每级之间分辨率突变的情况?csm每渲染一帧都要渲染4&nbsp;or&nbsp;8张阴影图吗?这样性能开销过大,怎么解决?(帧间)遮挡剔除的实现算法介绍一些?AA算法,RTGI算法介绍一些?原神是如何处理实时全局光照的你了解吗,均匀的在场景内布置光照探针如何应付大场景渲染?你是如何分析性能瓶颈,统计性能情况并进行优化的,怎么看出你实现了优化?bsdf和brdf的区别?各自的应用场景?介绍一些gpu&nbsp;driven的方法?半透物体如何在延迟管线中渲染(此题有坑)?(忘了,想起来再更)总结:一半的面试官会提到原神,建议去好好看看原神的图形算法实现(好像某乎上有大佬介绍)。我自己项目中大量使用compute&nbsp;shader但从未被问到(所以引擎岗不会太侧重编写shader的细节)。面试官还都特别喜欢问场景题,只能说纯背八股做项目是不行的,还是要多看别人文章,自己多思考总结举一反三。还有,一定要学一下renderdoc!这也是基础! #牛客解忧铺# #我的成功项目解析# #如何判断面试是否凉了#
点赞 评论 收藏
转发
游戏客户端&nbsp;&nbsp;&nbsp;&nbsp;暑期实习1.Blinn&nbsp;Phone模型&nbsp;计算光照强度是怎么做的吗?2.给出法线,平行光方向,怎么计算平行光强度呢?3.点乘叉乘区别4.MSAA抗锯齿的实现原理是什么?5.MSAA可以在延迟渲染上做吗?6.MSAA带宽为什么会增加?7.使用MSAA要避免什么操作?8.深度测试,模板测试具体是做了什么?9.法线贴图的作用是什么?法线贴图里面存的数据是什么?在Shader里面怎么用法线计算光照的?10.阴影贴图,深度值是怎么生成的?11.阴影抖动是什么问题导致的?12.阴影粉刺?13.数组和链表的区别?14.vector,添加元素到vector超过最大数目后会发生什么?15.介绍一下红黑树,有哪些数据结构用红黑树实现的16.智能指针,弱指针,假如共享指针已经释放掉了,弱指针会怎么样呢?17.共享指针的计数器是怎么实现的?18.两个共享指针指向一个对象,有几个计数器?&nbsp;&nbsp;&nbsp;&nbsp;C++并发编程部分(简历上有提到)19.什么是原子操作?20.i++是原子操作吗?++i是原子操作吗?21.线程同步的方法?线程1需要线程2的结果,怎么操作?22.new和malloc的区别?23.如果有多层for循环,如何从最里层跳出来。(我说break,然后说只能跳出一层,然后goto,要求列举风险)24.有20多个bool值数据,如果有一个是true,则满足条件,怎么只判断一次就成立?性能优化25.多态怎么实现?怎么实现虚函数的?怎么通过指针找到派生类的虚函数?26.C++怎么实现RTTI?27.静态类型转换和动态类型转换区别?我真是个傻子,上次面试有点吓到我了,第一题都没听明白什么意思,听录音才知道我有多么傻更新,已挂,但不是秒挂,比起上次有进步
点赞 评论 收藏
转发
28 64 评论
分享
牛客网
牛客企业服务