第一个主题,什么是0/1背包问题?
经典的0-1背包问题描述如下:给定具有 个物品的物品
集和具有容量C的背包,其中每个物品具有价值P 和重量
W ,要求选取物品集的一个子集,使得它们的总价值达到最大
并且重量之和小于等于背包容量C。引进二元变量X,,如果
选取了物品,则X =1,否则X =0。用数学表达式描述这个问
月 "
题:maximize z= PiX ,且满足约束: W, ≤C,其中X,∈
{O,1},,∈{l_2,⋯, }。O-1背包问题在信息加密 。 、预算控
制、项目选择、材料切割、货物装载、网络信息安全等应用中具
有重要的价值。所以,对O-1背包问题求解方法的研究无论是
在理论上还是在实践中都具有一定的意义。
第二个主题,0/1背包问题属于NP问题范畴。关于NP问题,有以下几个特点。
第一,分析时间复杂度的时候,可能会误以为是多项式级的,其实NP问题是伪多项式,即在一定规模内的问题求解,确实可以在多项式的时间内解决,但到一定区域,复杂度会随着规模增大而剧增。我们的任务就是尽量将这一区域,向右推。
第二,NP问题,往往是quick check的,即可以快速检验结果是否为最优。
下面进入主题——开始介绍针对不同规模的0/1背包问题设计不同的算法。
很多人可能一开始就想到使用贪心法,即尽可能的把性价比最高的放入背包。首先,评价一下这个,贪心法最大短板就是,所求结果并不一定是最优解。只有当问题变成连续的才会保证是最优解。
下面介绍求解0/1背包的标准解法——动态规划。
动态规划的总体思想是bottom up,即避免重复计算。即计算背包容量从0~C,物品从0到N。如果使用二维数组来实现的话,自然得到一张以物品为列,以容量为行的表。
那么我们在填这个表要遵循什么规则呢?
首先如果当前容量小于当前物品重量,则我们只好将上一行的值抄下来。
如果,当前容量大于等于当前物品重量,那么,我们需要权衡一下,不放这个物品(即左边一格的值)和放入这个物品后的值哪个更大,(放入物品之后的值,即为当前物品价值加上(先左移一格,然后向上移动weights[当前物品重量])),我们将较大者填入该格即可。
表的最右下角的数值即为最优解。
那么,我们如何获取到放入背包的物品呢?
那么我们就从最右下角的最优解出发。
首先,把它和它左边一格的值比较。 --第一步
如果相等,说明最后一个物品没有放入,继续向左,直到不相等,跳到第三步 --第二步
不相等,说明当前物品是在背包中的,故我们要去掉这个物品,体现在表中,就是首先向左移动一格,然后向上移动weights[i]格,即使用当前容量减去当前物品的重量。然后重复第一步 --第三步
直到移动到表的左上角,如果在这个过程中画上箭头,就会发现一个完整的路径就出来了!
这种二维数组实现的动态规划,很浪费内存。为什么呢?因为相同元素数的二维数组和一维数组比起来,二维数组占更大的内存。
写到这儿,大家应该知道,我们的第一次优化,就是将二维数组改成一维数组,怎么改呢?
其实我们就像是把刚刚形成的那张表,按列割开,割成一条一条的,然后首尾相连,就构成了一维数组。这里,需要我们做的事情,就是将上面提到的填写表的规则和寻找路径的规则映射到我们这个一维数组上,计算好index就ok。
这里插一块内容,其实,0/1背包问题解决办法很多,在对上面进行进一步优化前,我觉得介绍一下一种其他算法的思想很有必要,免得大家思路堵死。
介绍一种branch and bound方法,即分支定界法。
我们在寻找最优解的过程,其实可以这样来找,首先取第一个物品,放进背包是一种情况,不放背包是一种情况。在第一个物品放进背包的情况下,第二个物品又分为这两种情况,对于第一个物品不放入背包同样道理。所以,我们可以不断branch下去,递归出所有情况,找最优。
但是,我们在找的过程中会感觉到其实有一部分情况下,不用继续branch下去,因为我们都可以感觉到再往下也不如前面哪个情况更最优。我们的这种感觉怎么来定量呢?这里有一种办法就是我们使用bound,即计算点的上界值,如果这个上界值已经比前面的某种情况小了,我们就没必要继续branch。关于上界值计算,这里提供一种方法。我们可以放松一个条件,例如,我们可以把0/1背包改为0~1背包,这样就是连续的情况,我们可以计算出每个物品的性价比,然后尽量把性价比高的物品放入背包,如果放不下一个整的,就放0.几个该物品,这样我们的bound就有了,凡是bound小于前面某种情况的时候,我们就不在branch下去。
好,回到刚才的内容,我们刚才使用一维数组优化了二维数组实现的动态规划,但是,如果你使用java编写,虚拟机内存默认大概为64M,假设我们使用int数组,我们可以知道,我们这种办法最大解决的规模。这还远远不够!
关于继续优化的办法有很多,这里介绍一种,我自己实现过的一种。
这一种办法参考了《求解0-1背包问题的一种新混合算法》这一论文,大家下面可以自己去看这个论文。不喜欢看论文的就听我简单叙述一下这种算法。
首先,这个算法叫 “高效内存的动态规划和分治策略的混合算法”,算法效果还很好的。该混合算法的时间复杂度为O(nC),但是空间复杂度仅为O(上界函数【n/d】 +c),大约是一般动态规划算法的1/n。
我分开介绍。
第一个“高效内存的动态规划”。
我在描述动态规划的方法时,你也许会发现如果只要求计算最优解,其实两个长度为capacity的数组就够了,这种巨省内存的方法的短板就是没法儿求路径。高效内存就是使用两个长度为caoacity的数组的动态规划。
第二个分治策略
怎么分治呢?一个问题,我们可以取前m个为一个子问题,取m~到2m个为一个子问题。使用上面的动态规划,计算出相当于我们上面那个表的最后一列,然后,我要找到最优化的分割点,即最优化的capacity分割点,就是分别分给第一个子问题和第二个子问题多少容量,才能使V(first)+V(second)最大化。
我们就这样不断递归地分,直到子问题被分成单个物品,然后根据是否符合已经计算出的最优解来决定是否将当前物品放入背包。
需要注意的是,那篇论文在写高效内存动态规划的伪代码部分有误,最后的混合算法也有一定错误,稍不注意就无限递归下去了。另外,第二个子问题的计算和第一个子问题的计算并不一样,我需要修改高效内存的动态规划,添加起始点。对方法里的变量做不同的初始化。
额,码了这么多好累,csdn也不好上传图片,大家就将就着看吧。我就是想把这几天的思考过程在csdn做个备份,嘻嘻~