01背包问题的多种解法.docx
- 文档编号:1958594
- 上传时间:2022-10-25
- 格式:DOCX
- 页数:12
- 大小:453.55KB
01背包问题的多种解法.docx
《01背包问题的多种解法.docx》由会员分享,可在线阅读,更多相关《01背包问题的多种解法.docx(12页珍藏版)》请在冰豆网上搜索。
01背包问题的多种解法
一、问题描述
0/1背包问题:
现有n种物品,对1<=i<=n,已知第i种物品的重量为正整数Wi,价值为正整数Vi,背包能承受的最大载重量为正整数W,现要求找出这n种物品的一个子集,使得子集中物品的总重量不超过W且总价值尽量大。
(注意:
这里对每种物品或者全取或者一点都不取,不允许只取一部分)
二、算法分析
根据问题描述,可以将其转化为如下的约束条件和目标函数:
于是,问题就归结为寻找一个满足约束条件
(1),并使目标函数式
(2)达到最大的解向量。
首先说明一下0-1背包问题拥有最优解。
假设是所给的问题的一个最优解,则是下面问题的一个最优解:
。
如果不是的话,设是这个问题的一个最优解,则,且。
因此,,这说明是所给的0-1背包问题比更优的解,从而与假设矛盾。
穷举法:
用穷举法解决0-1背包问题,需要考虑给定n个物品集合的所有子集,找出所有可能的子集(总重量不超过背包重量的子集),计算每个子集的总重量,然后在他们中找到价值最大的子集。
由于程序过于简单,在这里就不再给出,用实例说明求解过程。
下面给出了4个物品和一个容量为10的背包,下图就是用穷举法求解0-1背包问题的过程。
(a)四个物品和一个容量为10的背包
序号
子集
总重量
总价值
序号
子集
总重量
总价值
1
空集
0
0
9
{2,3}
7
52
2
{1}
7
42
10
{2,4}
8
37
3
{2}
3
12
11
{3,4}
9
65
4
{3}
4
40
12
{1,2,3}
14
不可行
5
{4}
5
25
13
{1,2,4}
15
不可行
6
{1,2}
10
54
14
{1,3,4}
16
不可行
7
{1,3}
11
不可行
15
{2,3,4}
12
不可行
8
{1,4}
12
不可行
16
{1,2,3,4}
19
不可行
(b)用回溯法求解0-1背包问题的过程
递归法:
在利用递归法解决0-1背包问题时,我们可以先从第n个物品看起。
每次的递归调用都会判断两种情况:
(1)背包可以放下第n个物品,则x[n]=1,并继续递归调用物品重量为W-w[n],物品数目为n-1的递归函数,并返回此递归函数值与v[n]的和作为背包问题的最优解;
(2)背包放不下第n个物品,则x[n]=0,并继续递归调用背包容量为W,物品数目为n-1的递归函数,并返回此递归函数值最为背包问题的最优解。
递归调用的终结条件是背包的容量为0或物品的数量为0.此时就得到了0-1背包问题的最优解。
用递归法解0-1背包问题可以归结为下函数:
第一个式子表示选择物品n后得到价值比不选择物品n情况下得到的价值小,所以最终还是不选择物品n;第二个式子刚好相反,选择物品n后的价值不小于不选择物品n情况下得到了价值,所以最终选择物品n。
在递归调用的过程中可以顺便求出所选择的物品。
下面是标记物品被选情况的数组x[n]求解的具体函数表示:
在函数中,递归调用的主体函数为KnapSack,m表示背包的容量,n表示物品的数量,x[n]表示是否选择了第n个物品(1—选,0—不选)。
每个物品的重量和价值信息分别存放在数组w[n]和v[n]中。
具体的代码见《递归法》文件夹。
贪心法:
0-1背包问题与背包问题类似,所不同的是在选择物品装入背包时,可以选择一部分,而不一定要全部装入背包。
这两类问题都具有最优子结构性质,相当相似。
但是背包问题可以用贪心法求解,而0-1背包问题却不能用贪心法求解。
贪心法之所以得不到最优解,是由于物品不允许分割,因此,无法保证最终能将背包装满,部分闲置的背包容量使背包单位重量的价值降低了。
事实上,在考虑0-1背包问题时,应比较选择物品和不选择物品所导致的方案,然后做出最优解。
由此导出了许多相互重叠的子问题,所以,0-1背包问题可以用动态规划法得到最优解。
在这里就不再用贪心法解0-1背包问题了。
动态规划法分析:
0-1背包问题可以看作是寻找一个序列,对任一个变量的判断是决定=1还是=0.在判断完之后,已经确定了,在判断时,会有两种情况:
(1)背包容量不足以装入物品i,则=0,背包的价值不增加;
(2)背包的容量可以装下物品i,则=1,背包的价值增加。
这两种情况下背包的总价值的最大者应该是对判断后的价值。
令表示在前i个物品中能够装入容量为j的背包的物品的总价值,则可以得到如下的动态规划函数:
式
(1)说明:
把前面i个物品装入容量为0的背包和把0个物品装入容量为j的背包,得到的价值均为0.式
(2)第一个式子说明:
如果第i个物品的重量大于背包的容量,则装入第i个物品得到的最大价值和装入第i-1个物品得到的最大价值是相同的,即物品i不能装入背包中;第二个式子说明:
如果第i个物品的重量小于背包的容量,则会有两种情况:
(1)如果把第i个物品装入背包,则背包中物品的价值就等于把前i-1个物品装入容量为的背包中的价值加上第i个物品的价值;
(2)如果第i个物品没有装入背包,则背包中物品的价值就是等于把前i-1个物品装入容量为j的背包中所取得的价值。
显然,取二者中价值较大者作为把前i个物品装入容量为j的背包中的最优解。
我们可以一步一步的解出我们所需要的解。
第一步,只装入第一个物品,确定在各种情况下背包能得到的最大价值;第二步,只装入前两个物品,确定在各种情况下的背包能够得到的最大价值;一次类推,到了第n步就得到我们所需要的最优解了。
最后,便是在容量为W的背包中装入n个物品时取得的最大价值。
为了确定装入背包的具体物品,从的值向前寻找,如果>,说明第n个物品被装入了背包中,前n-1个物品被装入容量为的背包中;否则,第n个物品没有装入背包中,前n-1个物品被装入容量为W的背包中。
依此类推,直到确定第一个物品是否被装入背包为止。
由此,我们可以得到如下的函数:
.
根据动态规划函数,用一个的二维数组C存放中间变量,表示把前i个物品装入容量为j的背包中获得的最大价值。
设物品的重量存放在数组w[n]中,价值存放在数组v[n]中,背包的容量为W,数组存放迭代的结果,数组x[n]存放装入背包的物品,动态规划解0-1背包问题的源代码在文件夹《动态规划法》中。
回溯法分析:
用回溯法解0_1背包问题时,会用到状态空间树。
在搜索状态空间树时,只要其左儿子结点是一个可行结点,搜索就进入其左子树。
当右子树有可能包含最优解时才进入右子树搜索,否则将右子树剪去。
设r是当前剩余物品价值总和;cp是当前价值;bestp是当前最优价值。
当cp+r≤bestp时,可剪去右子树。
计算右子树中解的上界可以用的方法是将剩余物品依其单位重量价值排序,然后依次装入物品,直至装不下时,再装入该物品的一部分而装满背包。
由此得到的价值是右子树中解的上界,用此值来剪枝。
为了便于计算上界,可先将物品依其单位重量价值从大到小排序,此后只要顺序考察各物品即可。
在实现时,由MaxBoundary函数计算当前结点处的上界。
它是类Knap的私有成员。
Knap的其他成员记录了解空间树种的节点信息,以减少函数参数的传递以及递归调用时所需要的栈空间。
在解空间树的当前扩展结点处,仅当要进入右子树时才计算上界函数MaxBoundary,以判断是否可以将右子树减去。
进入左子树时不需要计算上界,因为其上界与父结点的上界相同。
在调用函数Knapsack之前,需要先将各物品依其单位重量价值从达到小排序。
为此目的,我们定义了类Objiect。
其中,运算符与通常的定义相反,其目的是为了方便调用已有的排序算法。
在通常情况下,排序算法将待排序元素从小到大排序。
在搜索状态空间树时,由函数Backtrack控制。
在函数中是利用递归调用的方法实现了空间树的搜索。
具体的代码见《回溯法》文件夹。
限界分支法:
在解0-1背包问题的优先队列式界限分支法中,活结点优先队列中结点元素N的优先级由该结点的上界函数MaxBoundary计算出的值uprofit给出。
该上界函数在0-1背包问题的回溯法总已经说明过了。
子集树中以结点N为根的子树中任一个结点的价值不超过N.profit。
因此我们用一个最大堆来实现活结点优先队列。
堆中元素类型为HeapNode,其私有成员有uprofit,profit,weight,level,和ptr。
对于任意一个活结点N,N.weight是活结点N所相应的重量;N.profit是N所相应的价值;N.uprofit是结点N的价值上界,最大堆以这个值作为优先级。
子集空间树中结点类型为bbnode。
在分支限界法中用到的类Knap与0-1背包问题的回溯法中用到的类Knap很相似。
他们的区别是新的类中没有了成员变量bestp,而增加了新的成员bestx。
Bestx[i]=1,当且仅当最优解含有物品i。
在类Knap中有四个函数:
(1)上界函数MaxBoundary(),计算节点所对应价值的上界;
(2)函数AddLiveNode()是将一个新的活结点插入到子集树和优先队列中;
(3)函数MaxKnapsack()实施对子集树的优先队列式分支界限搜索。
其中假定物品依其单位价值从大到小已经排好序。
相应的排序过程会在算法的预处理部分完成。
算法中E是当前扩展结点;cw是该结点的重量;cp是该结点的价值;up是价值上界。
算法的while循环不断扩展结点,直到子集树的一个叶结点成为扩展结点为止。
此时优先队列中所有活结点的价值上界均不超过该叶结点的价值。
因此该叶结点相应的解为问题的最优解。
在while循环内部,算法首先检查当前扩展结点的左儿子结点的可行性。
如果该左儿子结点是可行结点,则将它加入到子集树和活结点优先队列中。
当前扩展结点的右儿子结点一定是可行结点,仅当右儿子结点满足上界约束时才将它加入子集树和活结点优先队列。
(4)函数Knapsack()完成对输入数据的处理。
其主要任务是将各物品依其单位重量价值从达到小排好序。
然后调用函数MaxKnapsack()完成对子集树的优先队列式分支限界搜索。
具体的实现代码在文件夹《分支限界法》中。
三、时空效率分析
穷举法:
对于一个有n个元素的集合,其子集数量为,所以,不论生成子集的算法效率有多高,穷举法都会导致一个的算法。
递归法:
在递归法的算法体中有一个if判断中出现了两次递归调用比较大小所以它们之间的递归关系式可以大体表示为:
,其中表示递归法的时间复杂度,C是常数。
求解递归方程可以知道的量级为。
所以递归法解0-1背包问题的时间复杂度为。
递归法是耗费空间最多的算法,每次递归调用都需要压栈,导致栈的使用很频繁。
动态规划法:
由于函数Knapsack中有一个两重for循环,所以时间复杂度为O[(n+1)x(m+1)].空间复复杂度也是O[(n+1)x(m+1)],即O(nm).
回溯法:
由于计算上界的函数MaxBoundary需要O(n)时间,在最坏情况下有个右儿子结点需要计算上界,所以解0-1背包问题的回溯法算法BackTrack所需要的计算时间为.
限界分支法:
在使用限界分治法时,就是使用更好的限界剪枝函数使得不必要的解被剔除,但是在最坏情况下的解仍然是和回溯法是相同的。
本算法中也是用到了计算上界的函数MaxBoundary需要O(n)的时间,而且在最坏情况下有个结点需要计算上界,所以在最坏情况下的时间复杂度仍然为。
四、运行结果
递归法输出结果:
动态规划法输出结果:
回溯法输出结果:
分枝限界法输出结果:
五、分析输出结果
上面测试的是每种算法在两种输入情况下得到的0-1背包问题的解。
两种测试数据为:
第一组:
背包容量:
18;物品数目:
7;
每个物品重量为:
11248
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 01 背包 问题 多种 解法