• [置顶] 白话01背包


    前言:我想这些应该会让一个刚接触01背包的童鞋有收获!诚恳的希望有人指出我写的不清楚的地方。一起讨论。。

    学习dp一周左右啦,关于01背包前前后后看了很多次,只是把一个模板死记下来,却完全不能理解,也看到很多前辈为了有助理解做的的一些表格,但是感觉还是迷糊,哎哎。。只怪自己脑子不够用....后面一次花了几个小时模拟了一次过程。这里作为一次详细的笔记,也作为一个分享:(前面的文字是比较繁琐,但是需要沉下心看,后面结合图表就容易理解也不那么烦人啦大笑

    首先关于01背包问题:

    有N个物品,每个物品(只有唯一一个) i 对应有重量w[i]、价值va[i]。有一个背包可以放M重的物品,现在让你从N个物品中选择几个物品(每单个物品要么选择,要么不选择),在不超过背包上限情况使得背包装的价值最大。

    分析:

    如果你做过普通背包问题的话,可能第一个想到的就是贪心,按每件物品的价值比(价值/重量)从大到小排序,如果价值比一样就按价值从大到小排序,那样就可以每次选所有物品中价值比最大的,但是不幸的是这样的做法就错啦,用一个例子来说:有4件物品,背包最多放10kg,每件物品重量:1、4、6、8,价值1、4、6、8,(这里将价值和重量看成一样便于理解),他们的价值比都是1,但是为了贪到价值最大的,就按价值从大到小排序:8、6、4、1;然后先选择8,此时背包只能放2kg物品,你不得不选价值、重量为1的,那么你得到的价值之和为9,然而最优的确实中间两件物品:4、6,刚好不超过背包承受重量,价值为10大于那种贪心的做法!

    -------------------------------------------------------------------------------------------------------------------------------------------------------

    于是这里我们引进了01背包,动态规划,下面正式开讲:

    首先,关于背包的计算有一个状态转移方程(自己开始老是理解不了),等会先给出,然后结合例子说,还有就是一个一维(叫做“滚动数组”)的数组还有一个二维的数组,这里先从二维的写起,个人觉得起步时理解二维的要容易!!

    状态转移方程:dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+va[i]);   那么先解释这个方程的意思,开始没懂不要紧,后面结合图表理解。。dp[i][j]表示:把第 i 件物品放入空间大小为 j 的背包的最大价值 ,w[i]表示第 i 件物品的重量,va[i] 表示第 i 件物品的价值;

    下面给出这个二维数组的计算过程,(这也只是很多种其中的一种):(开始没理解也不要紧)

    for(i=1;i<=N;i++)  //N个物品、背包最大容量M、w[i]i的重量、va[i]i的价值
    	for(j=M;j>=w[i];j++) //有的计算过程是 for(j=1;j<=M;j++) 都是一样的
    	{
    		if(dp[i-1][j]<dp[i-1][j-w[i]]+va[i])//这里就是dp[i][j]状态转移方程取最大的过程
    			dp[i][j]=dp[i-1][j-w[i]]+va[i];
    		else
    			dp[i][j]=dp[i-1][j];
    	}

    下面模拟过程:

    例子:4个物品、背包上限6
    第一个:w[1]=1、va[1]=4

    第二个:w[2]=2、va[2]=6

    第三个:w[3]=3、va[3]=12

    第四个:w[4]=2、va[4]=7

    下面就是dp[i][j]数组的图表:并且初始化为0(这里并没有全部填写为0,实际上是全部填写为0啦)

    此时(结合代码)是for循环的开始,第一个我们要填充的就是dp[1][6](图中黑色圈圈标记的格子),也就是求:用第1个物品放入背包空间为6的最大值,按照上面的方程,这个最大值是从

    dp[0][6]和dp[0][6-w[1]]+va[i]两个值中选出来:(看到这里大概明白为什么dp开始初始化要为0,并且两个for循环下标都是从1开始的)

    那么选择之后便是:(最大值是dp[1][5]+4,也就是4)

    接着继续填充下一个j=M之后的j--的格子,也就是dp[1][5],依此填完这一列之后的结果:

    之后就填写第二行的,并且注意,填写的格子的值由两根线指向中选择,但红色的那根线是 dp[2][6-w[2]]还要加上一个va[2]的值,然后和dp[1][6]比较,选择较大的值填入,那么这一行填完的结果便是:

    这里也有一个地方要注意,我们有一个格子dp[2][1]没有填写,因为第二个for循环的结束条件就是j>=w[i],解释为背包的容量大于等于 i 物品的重量才去放,不然你放都放不进就没有必要放啦。

    那么等到最后,结果便是下面:

    红色的就是没有填写过的,也就是初始化的值为0,只是在开始没有全部填写上0,最后,dp[4][6]的意思就是把4个物品都尝试放入背包上限为6kg的最大价值:即23.....那么01背包就是这么实现的。-------------------------------

    小结:如果仔细看了上面的过程的话,那么多多少少应该有些理解过程了吧 !那么总结一下,本次总结解决状态转移方程的含义,至于for循环的实现过程应该在模拟的过程都明白了吧

    dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+va[i]),用我们的那种 j 从M递减到 w[i]的方式,可以这样理解:最开始,背包里什么都没放,然后我们考虑背包容量为M到w[i]的情况(小于w[i]的意思就是背包装不下去,就没必要考虑),对于一个容量 为 j 的背包A,考虑第 i 个物品,它当前有一个值dp[i][j](最开始什么都没放都是0),然后我们就想 如果我们找一个容量为 j-w[i]背包B装 第 i个物品,意思是装第 i 件物品使得背包B容量为 j (可以这样看:那个背包B原来容量dp[i-1][j-w[i]] ,现在放入 i 物品,容量变为dp[i-1][ ),那么,对于B,它现在的价值就是原来价值dp[i-1][j-w[i]]加上刚装进的 i 的价值 va[i],那么现在就做一个选择,是A的价值多还是B的价值多,我们就选择价值多的更新A,如果B大就为B,因为我们可以找到一个背包B,使得达到相同的背包容量 j 的时候价值更大。

    ——-------------------------------------------------------------------------理解了上面,那么下面就差不多啦...

    那么,前面收到上面的那个计算过程代码只是一种,如果理解了上面的那种,那么下面的这种也就是一样的

    for(i=1;i<=N;i++)
    	for(j=1;j<=M;j++)//不同的地方是这里从1 到M
    	{
    		if(w[i]<=j) //这里因为背包从1到M,那么就要判断一下能否放入,放不进就把这个格子的值等于上面格子的值,也就是说这个物品放不进,只能放上面那件物品,价值也就上面的格子
    		{
    			if(dp[i-1][j]<dp[i-1][j-w[i]]+va[i])
    				dp[i][j]=dp[i-1][j-w[i]]+va[i];
    			else
    				dp[i][j]=dp[i-1][j];
    		}
    		else
    			dp[i][j]=dp[i-1][j];
    	}

    .....-----------------------------------------------

    那么,如果比较清楚上面的实现过程啦...就可以初步了解用一维数组(既滚动数组)

    仔细理解上面的过程,不难发现,我们每填写一行的时候,都是根据上一行的两个格子俩确定的...那么可以想象:我们用二维数组是不是浪费空间了呢?

    那么 先来看看一维的状态转移方程:dp[j]=max(dp[j],dp[j-w[i]]+va[i]);表示:把第i个物品放入背包容量为j的背包的最大价值,

    实现过程代码:

    for(i=1;i<=N;i++)//注意这里只是对空间进行优化,而两个for循环是一定的
    	for(j=M;j>=w[i];j--)
    		if(dp[j]<dp[j-w[i]]+va[i])
    			dp[j]=dp[j-w[i]]+va[i];

    那么现在也来模拟一下

    还是上面的例子:4个物品、背包上限6
    第一个:w[1]=1、va[1]=4

    第二个:w[2]=2、va[2]=6

    第三个:w[3]=3、va[3]=12

    第四个:w[4]=2、va[4]=7

    当进行第一行填写的时候,如下

     填完第一个后之后的每次 就用 dp[j]和dp[j-w[i]]+va[i]比较,选择大的更新dp[j],这样就可以节省很大的空间,

    那么,值得注意的便是,用一维数组,j 只能从M到w[i] 递减,为什么呢?每次比较都是用本身dp[j],和前面的dp[j-w[i]]比较,并且值更新dp[j]的值, 那么,对于这一次的for循环,开始更新dp[j],随后j--,要填写和更新的dp[j]并没有受到刚刚的影响(这个容易懂吧......)但是一旦 j 像二维dp那样从1 到M递增,填写后面的数值很明显就被前面的影响啦。。。。。。。好吧至此,01背包总结完毕,为了方便理解,全部纯手工打造,比自己学习01背包的时间花的还长哭,不过希望对大家有帮助,也希望自己下次来看便可以很快了解.....如果有没说的清楚的地方可以指出讨论........转载谢谢合作指明出处...(如果有人愿意转载我这菜鸟的///////偷笑

    ps:最后给几个我最近做的01背包题目 首先最最简单的裸题:poj3624、然后有一个poj3628,还有一个hdu1864,还有一个poj3211,个人认为这几个题目的顺序就是难度上升的梯度..这些题目也会在我的博客给出结题报告...

  • 相关阅读:
    数据库范式
    将DBF,XLS,XML,MDB文件导入C#DataGrid的方法
    在类文件中引用Server对象
    在使用了母版页的页面里加载css和js文件
    IIS 7.5 URL重写参数
    hdu Can you solve this equation?
    hdu Dome of Circus
    hdu Bone Collector
    hdu Turn the corner
    hdu 悼念512汶川大地震遇难同胞——珍惜现在,感恩生活
  • 原文地址:https://www.cnblogs.com/keanuyaoo/p/3266594.html
Copyright © 2020-2023  润新知