背包(01背包、完全背包、多重背包)问题总结

来源:互联网 发布:linux mint安装教程 编辑:程序博客网 时间:2024/04/28 14:41

以前也写过01背包问题的博文,但理解并不深刻,前几天在做HDOJ1059这道题的时候,在网上搜了下,原来背包问题远不止01背包问题那么简单,当然,01背包问题是基础。于是参考了网上非常经典的一篇文章:http://love-oriented.com/pack/#sec5


问题定义:

先看一看比较经典的三个背包问题的问题原型。

 

01背包问题:

         有N物品和一个容量为V的背包。第i个物品的所占容量为c[i],价值为w[i],求解将哪些物品装入背包可使这些物品的总费用不超过背包容量,且使得总价值最大。

 

完全背包问题:

         有N物品和一个容量为V的背包,每种物品的数量不限。第i种物品的费用是c[i],价值是w[j],求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且总价值最大。

 

多重背包问题:

         有N物品和一个容量为V的背包,第i种物品的数量为num[i],费用是c[i],价值是w[i],求解将哪些物品装入背包可使这些物品的总费用不超过背包容量,且使得总价值最大。

 

问题分析

01背包问题

         01背包问题是最基本的背包问题,后面的完全背包问题和多重背包问题其实都可以转化为01背包问题。

         对于01背包问题,其特点是:每一个物品仅有一件,对于每一个物品是否放入相当于一个选择。用子问题定义状态:f[i][v]表示前i件物品放入一个容量为v的背包可以获得的最大价值。于是我们不难得出其状态转移方程:

f[i][v] = max { f[i-1][v] ,f[i-1][v-c[i]] + w[i] }

下图是为了便于理解的一个状态表。


图1 状态表

         这是一个经典的状态转移方程,几乎所有的背包问题都可以转化为这个方程求解。然而,此方法的时间和空间复杂度都是(V*N)的,对于时间复杂度我们已经无法再优化了,但对于空间复杂度却可以优化到O(V)。

         首先考虑上面的状态转移方程是如何实现的,其中必然有一个主循环i=1…N,每次算出二维数组f[i][0…v]的值。那么,如果使用一维数组f[0…V],在第i次主循环结束后,能否保证f[v]的值就是f[i][v]的值呢?我们可以看到,f[i][v]是f[i-1][v]和f[i-1][v-c[i]]+w[i]中较大的值,事实上,只要我们在每次主循环中保证v的值是由V…0的顺序来推导f[v]的,就能保证我们在第i次推导f[v]的时候,f[v-c[i]]的值就等于f[i-1][v-c[i]]。其伪代码如下:

 

for i = 0…N

         forv = V…0

                   f[v]= max { f[v] , f[v-c[i]] + w[i] }

 

事实上:使用一维数组解01背包问题的程序会在后面被多次用到,所以这里抽象出一个处理一件01背包中的物品的函数。

Void ZeroOnePack( cost, weight )

{

         forv=V…cost

                   f[v]= max { f[v], f[v-cost] }

}

所以01背包问题可以这样写:

Void ZeroOnePackProblem()

{

         fori = 1…N

                   ZeroOnePack(c[i], w[i] );

}

 

关于初始化细节的问题:

         对于01背包问题,往往有两种情况:其一、题目要求“恰好装满背包”的最优解;其二、题目不做这样的要求。

         对于第一种情况,我们需将第1次主循环之前的初值f[0]设为0, f[1…V]设为-∞即可。

         对于第二种情况,我们可以将第1次主循环之前的初值f[0…V]设为0就行了。

         解释是这样的:初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可能价值为0的nothing“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,故它们的值设为-∞。如果背包不用“恰好装满,则容量大于0的背包所装的物品也为0也是合法状态,故设置f[0…V]=0。

 

 

完全背包问题

         对于完全背包问题,与01背包问题的是每种物品可以选取无限件,但我们仍然可以用01背包问题的思路来解答:

f[i][v] = max {f[i-1][v-k*c[i]]+k*w[i] | 0<=k*c[i]<=v }

我们这里就将01背包问题的基本思路加以改进就得到了很清晰的解法,这足以说明01背包问题的重要性。当然,我们可以看到这个算法的时间复杂度是超过O(V*N)的。

 

一个简单有效的优化

    完全背包问题有一个很简单有效的优化,是这样的:若两件物品i、j满足c[i]<=c[j]且w[i]>=w[j],则将物品j去掉,不用考虑。这个优化的正确性显然:任何情况下都可将价值小费用高得j换成物美价廉的i,得到至少不会更差的方案。对于随机生成的数据,这个方法往往会大大减少物品的件数,从而加快速度。然而这个并不能改善最坏情况的复杂度,因为有可能特别设计的数据可以一件物品也去不掉。

    这个优化可以简单的O(N^2)地实现,一般都可以承受。另外,针对背包问题而言,比较不错的一种方法是:首先将费用大于V的物品去掉,然后使用类似计数排序的做法,计算出费用相同的物品中价值最高的是哪个,可以O(V+N)地完成这个优化。

 

转化为01背包问题求解

    既然01背包问题是最基本的背包问题,那么我们可以考虑把完全背包问题转化为01背包问题来解。最简单的想法是,考虑到第i种物品最多选V/c[i]件,于是可以把第i种物品转化为V/c[i]件费用及价值均不变的物品,然后求解这个01背包问题。这样完全没有改进基本思路的时间复杂度,但这毕竟给了我们将完全背包问题转化为01背包问题的思路:将一种物品拆成多件物品。更高效的转化方法是:把第i种物品拆成费用为c[i]*2^k、价值为w[i]*2^k的若干件物品,其中k满足c[i]*2^k<=V。这是二进制的思想,因为不管最优策略选几件第i种物品,总可以表示成若干个2^k件物品的和。这样把每种物品拆成O(log(V/c[i]))件物品,是一个很大的改进。

 

下面是一个更简单的算法,先看伪代码:

for i = 1…N

         forv = 0…V

                   f[v]= max { f[v] , f[v-c[i]]+w[i] }

不难发现,这个代码,与01背包问题的代码差距仅仅是v的赋值顺序不一样。

解释是这样的:在01问题中,v的赋值顺序是V…0,这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1][v-c[i]]推导而来,也就是说,这是为了保证每件物品只能选一次。而现在的完全背包问题的特点是每种物品可以选择无限件,所以在考虑“加选一件第i中物品“这种策略时,正需要一个可能已选入第i种物品的子结果f[i][v-c[i]],所以可以且必须采用v = 0…V的顺序循环。

这个算法也可以由以下的状态转移方程得出:

f[i][v] = max { f[i-1][v] ,f[i][v-c[i]]+w[i] }

所以,最后抽象出处理一件完全背包类物品的过程伪代码:

Void CompletePack(cost, weight)

{

         forv = cost…V

                   f[v]= max { f[v], f[v-c[i]] + w[i] }

}

 

 

多重背包问题

         多重背包问题的一个特点是每种物品的数量有限制,为num[i],但我们可以很容易发现其实多重背包问题与完全背包问题非常类似,因为对于所有的i当num[i]*c[i] > V的时候,其实就是完全背包问题。当然,我们也可以利用一下01背包的思想,得出以下的状态转换方程:

f[i][v] = max { f[i-1][v-k*c[i]] | 0<=k<=num[i] &&k*num[i]<V }

其时间复杂度是O(V*Σn[i))。

         当然,同上面的完全背包问题一样,我们同样可以采用二进制的方式来优化算法的时间复杂度。考虑k=1,2,4…,num[i],实际上,当k取这些可能值的时候已经包含了1,2,3,4…,num[i]。我们相当于把num[i]件物品分成消耗为2^k*c[i]的若干件物品。这样算法的时间复杂度就减为O(V*Σlog(n[i]))。

         下面是该方法的伪代码:

void MultiplePack(cost, weight, amount)

{

         ifcost*amount >= V

                   CompletePack(cost,weight);

         else

                  k=1;

                   whilek<num

                            ZeroOnePack(k*cost,k*weight);

                           amount-= amount – k;

                            K= k*2;

                   ZeroOnePack(amount * cost , amount*weight);

}


原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 爱派的密码忘了怎么办 苹果爱派密码忘了怎么办 鼠标无法识别的usb设备怎么办 电脑鼠标无法识别usb设备怎么办 win7电脑用户密码忘了怎么办 联想win7旗舰版开不了机怎么办 驱动都被卸载了怎么办 电脑密码忘了怎么办w7旗舰版 笔记本电脑密码忘了怎么办w7 windows一键还原了怎么办 戴尔笔记本电脑键盘没反应怎么办 win10电脑系统盘满了怎么办 win7玩dnf卡死怎么办 cf老是卡住闪退怎么办 w7系统帐户被停用怎么办 海康硬盘录像机密码忘了怎么办 电信合约套餐到期后怎么办 电脑更新系统卡住了怎么办 格力空调显示e1怎么办 ae崩溃了没保存怎么办 电脑下面的状态栏没了怎么办 游戏32位不兼容怎么办 电脑开机dos红屏怎么办 win7进入dos红屏怎么办 手机玩游戏屏幕卡住不动怎么办 魔域英文版换中文版怎么办 党员培养期不足一年怎么办 出生证明日期错了怎么办 毕业生登记表写错了怎么办 高等学校毕业生登记表写错了怎么办 眼睛里迷了东西怎么办 眼睛迷了怎么办小绝招 isf货物离港申报怎么办 非农户口没住房怎么办 在工厂上班很累怎么办 退货运费太贵了怎么办 悬肘写字手抖怎么办 护士成绩合格证明丢了怎么办 找工作真难找好烦怎么办 大夫说身体不适合怀孕有了怎么办 不知道要做什么工作怎么办