您好,欢迎来到知库网。
搜索
您的当前位置:首页算法分析与设计实验报告之01背包问题

算法分析与设计实验报告之01背包问题

来源:知库网
#

算法分析与设计实验报告

[0/1背包问题]

0/1背包问题的不同算法解决方案

组员02黄希龙 09455321张育强05周麒

目录

一.问题描述 ...................................................错误!未定义书签。

二.算法分析 .............................. 错误!未定义书签。

1.穷举法: ................................................错误!未定义书签。

2.递归法: ................................................错误!未定义书签。 3.贪心法: ................................................错误!未定义书签。 4.动态规划法分析: ........................................错误!未定义书签。 5.回溯法分析: ............................................错误!未定义书签。 6.分支限界法: ............................................错误!未定义书签。

}

三.时空效率分析 .......................... 错误!未定义书签。

1.穷举法: ................................................错误!未定义书签。 2.递归法: ................................................错误!未定义书签。 3.动态规划法: ............................................错误!未定义书签。 4.回溯法: ................................................错误!未定义书签。 5分支限界法: ............................................错误!未定义书签。

四.运行结果 .............................. 错误!未定义书签。

1.穷举法输出结果: ........................................错误!未定义书签。 ; 2.递归法输出结果: ........................................错误!未定义书签。 3.动态规划法输出结果: ....................................错误!未定义书签。 4.回溯法输出结果: ........................................错误!未定义书签。 5.分支限界法输出结果: ....................................错误!未定义书签。

五.分析输出结果 .......................... 错误!未定义书签。 六.总结与反思 ............................ 错误!未定义书签。

[

一.问题描述

0/1背包问题:

现有n种物品,对1<=i<=n,已知第i种物品的重量为正整数Wi,价值为正整数Vi,背包能承受的最大载重量为正整数W,现要求找出这n种物品的一个子集,使得子集中物品的总重量不超过W且总价值尽量大。(注意:这里对每种物品或者全取或者一点都不取,不允许只取一部分)

二.算法分析

根据问题描述,可以将其转化为如下的约束条件和目标函数:

nwixiW(1)i1x{0,1}(1in) imaxvixi(2)i1n于是,问题就归结为寻找一个满足约束条件(1),并使目标函数式(2)达到最大的解向量X(x1,x2,x3,......,xn)。

首先说明一下0-1背包问题拥有最优解。

假设(x1,x2,x3,......,xn)是所给的问题的一个最优解,则(x2,x3,......,xn)是下面问题的

nnwixiWw1x1一个最优解:i2maxvixi。如果不是的话,设(y2,y3,......,yn)是这

i2x{0,1}(2in)i个问题的一个最优解,则

vyvxiii2i2nnnii,且w1x1wyii2niW。因此,

v1x1viyiv1x1vixivixi,这说明(x1,y2,y3,........,yn)是所给的0-1背包问

i2i2i1nn题比(x1,x2,x3,........,xn)更优的解,从而与假设矛盾。

1.穷举法:

用穷举法解决0-1背包问题,需要考虑给定n个物品集合的所有子集,找出所有可能的子集(总重量不超过背包重量的子集),计算每个子集的总重量,然后在他们中找到价值最大的子集。由于程序过于简单,在这里就不再给出,用实例说明求解过程。下面给出了4个物品和一个容量为10的背包,下图就是用穷举法求解0-1背包问题的过程。

10W1=7V1=12W2=3V2=12背包物品1物品2W3=4V3=40W4=5V4=25物品3物品4

总重量 7 8 9 14 15 16 12 19 总价值 52 37 65 不可行 不可行 不可行 不可行 不可行 序号 1 2 3 4 5 6 7 8 子集 空集 {1} {2} {3} {4} {1,2} {1,3} {1,4} (a) 四个物品和一个容量为10的背包 . 总价值 序号 子集 总重量 $ 0 % 7 、 3 4 < 5 ` 10 ~ 11 ~ 12 穷举法代码如下: 0 42 12 40 25 不可行 不可行 9 10 11 12 13 14 15 16 {2,3} {2,4} {3,4} {1,2,3} {1,2,4} {1,3,4} {2,3,4} {1,2,3,4} #include #include #include#define MAX 100 // 限定最多物品/*将n化为二进制形式,结果存放到数组b中*/void conversion(int n,int b[MAX]){int i;for(i=0;i2.递归法:

在利用递归法解决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背包问题可以归结为下函数:

KnapSack(n,m)没有选择物品nKnapSack(n1,m)

KnapSack(n1,mw[n])v[n]选择了物品n 第一个式子表示选择物品n后得到价值KnapSack(n1,mw[n])v[n]比不选择物品n情况下得到的价值KnapSack(n1,m)小,所以最终还是不选择物品n;第二个式子刚好相反,选择物品n后的价值KnapSack(n1,mw[n])v[n]不小于不选择物品n情况下得到了价值KnapSack(n1,m),所以最终选择物品n。

在递归调用的过程中可以顺便求出所选择的物品。下面是标记物品被选情况的数组

x[n]求解的具体函数表示:

KnapSack(n,m)KnapSack(n1,m)0 x[n]

KnapSack(n,m)KnapSack(n1,mw[n])v[n]1 在函数中,递归调用的主体函数为KnapSack,m表示背包的容量,n表示物品的数量,x[n]表示是否选择了第n个物品(1—选,0—不选)。每个物品的重量和价值信息分别存放在数组w[n]和v[n]中。代码如下:

#includeusing namespace std;int cw=0,bp=0,m,c,i,j,v=0;//cw为当前背包中物品的重量,int price[1000];//记录物品的价值int weight[1000];//记录物品的重量int b[1000];//记录此个排列的状态int k[1000];//记录最优解时候的排列状态void Re(int i){ if(i>m) { if(bp>v) { for(j=1;j<=m;j++) k[j]=b[j]; v=bp; } return ;

3.贪心法:

0-1背包问题与背包问题类似,所不同的是在选择物品i(1in)装入背包时,可以选择一部分,而不一定要全部装入背包。这两类问题都具有最优子结构性质,相当相似。但是背包问题可以用贪心法求解,而0-1背包问题却不能用贪心法求解。贪心法之所以得不到最优解,是由于物品不允许分割,因此,无法保证最终能将背包装满,部分闲置的背包容量使背包单位重量的价值降低了。事实上,在考虑0-1背包问题时,应比较选择物品和不选择物品所导致的方案,然后做出最优解。由此导出了许多相互重叠的子问题,所以,0-1背包问题可以用动态规划法得到最优解。在这里就不再用贪心法解0-1背包问题了。

4.动态规划法分析:

0-1背包问题可以看作是寻找一个序列(x1,x2,x3,........,xn),对任一个变量xi 的判断是决定xi=1还是xi=0.在判断完xi1之后,已经确定了(x1,x2,x3,........,xi1),在判断xi时,会有两种情况:

(1) 背包容量不足以装入物品i,则xi=0,背包的价值不增加; (2) 背包的容量可以装下物品i,则xi=1,背包的价值增加vi。

这两种情况下背包的总价值的最大者应该是对xi判断后的价值。令C(i,j)表示在前i(1in)个物品中能够装入容量为j(1jW)的背包的物品的总价值,则可以得到如

C(i,0)C(0,j)0(1)下的动态规划函数:

C(i1,j)jwiC(i,j)(2)max{C(i1,j),C(i1,jwi)vi}jwi*

式(1)说明:把前面i个物品装入容量为0的背包和把0个物品装入容量为j的背包,得到的价值均为0.式(2)第一个式子说明:如果第i个物品的重量大于背包的容量,则装入第i个物品得到的最大价值和装入第i-1个物品得到的最大价值是相同的,即物品i不能装入背包中;第二个式子说明:如果第i个物品的重量小于背包的容量,则会有两种情况:(1)如果把第i个物品装入背包,则背包中物品的价值就等于把前i-1个物品装入容量为jwi的背包中的价值加上第i个物品的价值vi;(2)如果第i个物品没有装入背包,则背包中物品的价值就是等于把前i-1个物品装入容量为j的背包中所取得的价值。显然,取二者中价值较大者作为把前i个物品装入容量为j的背包中的最优解。

我们可以一步一步的解出我们所需要的解。第一步,只装入第一个物品,确定在各种情况下背包能得到的最大价值;第二步,只装入前两个物品,确定在各种情况下的背包能够得到的最大价值;一次类推,到了第n步就得到我们所需要的最优解了。最后,C(n,W)便是在容量为W的背包中装入n个物品时取得的最大价值。为了确定装入背包的具体物品,从C(n,W)的值向前寻找,如果C(n,W)>C(n1,W),说明第n个物品被装入了背包中,前n-1个物品被装入容量为Wwn的背包中;否则,第n个物品没有装入背包中,前n-1个物品被装入容量为W的背包中。依此类推,直到确定第一个物品是否被装入背包为止。由此,我们可以得到如下的函数:xi0C(i,j)C(i1,j).

1,jjwiC(i,j)C(i1,j) 根据动态规划函数,用一个(n1)(W1)的二维数组C存放中间变量,C[i][j]表示把前i个物品装入容量为j的背包中获得的最大价值。

设物品的重量存放在数组w[n]中,价值存放在数组v[n]中,背包的容量为W,数组

C[n1][W1]存放迭代的结果,数组x[n]存放装入背包的物品,动态规划解0-1背包问

题的源代码如下:

#include#includeusing namespace std;typedef float T;//templatevoid Traceback(int n,T w[],T v[],T p[][2],int *head{T j=p[head[0]-1][0],m=p[head[0]-1][1];for(int i=1;i<=n;i++){x[i]=0;for(int k=head[i+1];k<=head[i]-1;k++){if(p[k][0]+w[i]==j && p[k][1]+v[i]==m){x[i]=1;j=p[k][0];

5.回溯法分析:

用回溯法解0_1背包问题时,会用到状态空间树。在搜索状态空间树时,只要其左儿子结点是一个可行结点,搜索就进入其左子树。当右子树有可能包含最优解时才进入右子树搜索,否则将右子树剪去。设r是当前剩余物品价值总和;cp是当前价值;bestp是当前最优价值。当cp+r≤bestp时,可剪去右子树。计算右子树中解的上界可以用的方法是将剩余物品依其单位重量价值排序,然后依次装入物品,直至装不下时,再装入该物品的一部分而装满背包。由此得到的价值是右子树中解的上界,用此值来剪枝。

为了便于计算上界,可先将物品依其单位重量价值从大到小排序,此后只要顺序考察各物品即可。在实现时,由MaxBoundary函数计算当前结点处的上界。它是类Knap的私有成员。Knap的其他成员记录了解空间树种的节点信息,以减少函数参数的传递以及递归调用时所需要的栈空间。在解空间树的当前扩展结点处,仅当要进入右子树时才计算上界函数MaxBoundary,以判断是否可以将右子树减去。进入左子树时不需要计算上界,因为其上界与父结点的上界相同。

在调用函数Knapsack之前,需要先将各物品依其单位重量价值从达到小排序。为此目的,我们定义了类Objiect。其中,运算符与通常的定义相反,其目的是为了方便调用已有的排序算法。在通常情况下,排序算法将待排序元素从小到大排序。

在搜索状态空间树时,由函数Backtrack控制。在函数中是利用递归调用的方法实现了空间树的搜索。具体的代码如下:

int n=0; int i=0; system(\"title 0/1背包问题——回溯法\");system(\"color 2e\"); cout<<\"请输入物品个数和背包载重:\"<>n>>c; p=new int[n+1]; w=new int[n+1]; p[0]=0; w[0]=0; cout<<\"请输入个背包的价值和重量:\"<>p[i]>>w[i];cout<6.分支限界法:

在解0-1背包问题的优先队列式界限分支法中,活结点优先队列中结点元素N的优先级由该结点的上界函数MaxBoundary计算出的值uprofit给出。该上界函数在0-1背包问题的回溯法总已经说明过了。子集树中以结点N为根的子树中任一个结点的价值不超过。因此我们用一个最大堆来实现活结点优先队列。堆中元素类型为HeapNode,其私有成员有uprofit,profit,weight,level,和ptr。对于任意一个活结点N,是活结点N所相应的重量;是N所相应的价值;是结点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()完成对子集树的优先队列式分支限界搜索。

具体的实现代码如下:

* 输出:最优装入背包的物体obx[],装入背包的物体最优价值v */ float knapsack_bound(OBJECT ob[], float M, int n, i int i, k = 0; // 堆中元素个数的计数器初始化为0 float v; KNAPNODE *xnode, *ynode, *znode; HEAP x, y, z, *heap; heap = new HEAP[n*n]; // 分配堆的存储空间 for( i=0; i三.时空效率分析

1.穷举法:

n 对于一个有n个元素的集合,其子集数量为2,所以,不论生成子集的算法效率有多高,穷举法都会导致一个O(2)的算法。

n2.递归法:

在递归法的算法体中有一个if判断中出现了两次递归调用比较大小所以它们之间的递归关系式可以大体表示为:T(n)2T(n1)C,其中T(n)表示递归法的时间复杂度,C是常数。求解递归方程可以知道T(n)的量级为O(2)。所以递归法解0-1背包问题的时间复杂度为O(2)。递归法是耗费空间最多的算法,每次递归调用都需要压栈,导致栈的使用很频繁。

nn3.动态规划法:

由于函数Knapsack中有一个两重for循环,所以时间复杂度为O[(n+1)x(m+1)].空

间复复杂度也是O[(n+1)x(m+1)],即O(nm).

4.回溯法:

由于计算上界的函数MaxBoundary需要O(n)时间,在最坏情况下有O(2)个右儿子结点需要计算上界,所以解0-1背包问题的回溯法算法BackTrack所需要的计算时间为

nO(n2n).

5分支限界法:

.

在使用限界分治法时,就是使用更好的限界剪枝函数使得不必要的解被剔除,但是在最坏情况下的解仍然是和回溯法是相同的。本算法中也是用到了计算上界的函数MaxBoundary需要O(n)的时间,而且在最坏情况下有O(2)个结点需要计算上界,所以在最坏情况下的时间复杂度仍然为O(n2)。

nn四.运行结果

1.穷举法输出结果:

2.递归法输出结果:

3.动态规划法输出结果:

{

4.回溯法输出结果:

\"

5.分支限界法输出结果:

五.分析输出结果

上面测试的是每种算法在两种输入情况下得到的0-1背包问题的解。两种测试数据为: 第一组:背包容量:18; 物品数目:7;

每个物品重量为:11 2 4 8 9 6 10; 每个物品价值为:7 8 8 12 13 4 14。 第二组:背包容量:50; 物品数目:10;

每个物品重量为: 8 12 24 16 6 9 35 21 18 19; 每个物品价值为: 34 32 56 67 32 45 56 46 70。 五种实现的算法中,只有回溯法没能够得到预期的最优解。(但是可能是算法设计时的问题,其实回溯法是穷举法的变形,肯定能够得到最优解的,这里是我设计函数的问题。从递归法的输出可知,它的结果就是我们想要的最优解)。从时间复杂度和空间复杂度分析可知,动态规划法的时间复杂度是最小的,但是同时它的空间复杂度又是最大的。这里就可以看出在设计算法的过程中要考虑它们的平衡问题。在时间要求比较快的情况下,我们就可以选择动态规划法;在空间要求比较高时,我们就可以使用穷举法或是分枝限界法等其他改进的穷举法。 …

各种算法在解背包问题时的比较如下表所示:

算法名称 穷举法 递归法 动态规划法 时间复杂度 优点 [ 最优解 最优解 最优解 空间消耗大 速度慢 用数组存 递归方程求解 缺点 速度慢 改进 剪枝 O(2n) O(2n) O(nm) 贪心法 回溯法 分枝限界法 O(nlog2n) O(n2n) O(n2n) 不一定是最优解 最优解 最优解 速度快 速度慢 速度慢 可以作为启发 改进剪枝 优化限界函数 从计算复杂性理论看,背包问题是NP完全问题。半个多世纪以来,该问题一直是算法与复杂性研究的热门话题。通过对0-1背包问题的算法研究可以看出,回溯法和分枝限界法等可以得到问题的最优解,可是计算时间太慢;动态规划法也可以得到最优解,当m2时,算法需要O(n2)的计算时间,这与回溯法存在一样的缺点——计算速度慢;采用贪心算法,虽然耗费上优于前者,但是不一定是最优解。目前,以上几种方法中回溯法、动态规划法、贪心法都广泛地应用到不同的实际问题中,并在应用中不断地改进。

nn六.总结与反思

算法是建立在解法基础之上的,是在某个具体问题解法过程的分析之后,归纳出的解决一类相关问题的程序或步骤;如果一个具体问题具有代表性,其解法又具有程序性,那么这样的解法也能体现算法思想.解法是“授之以鱼”,即是对某个特定问题的解决过程,或者说解法是解决某一个问题的步骤,解法一般要有答案.算法是“授之以渔”,即是解决某一类问题的步骤,而且是实现人机联系的方法,有着明确性、有限性和有序性等特征,算法不一定要有答案(可以交给计算机解决).

算法的重要性不言而喻,而我们掌握的还太少,还有待我们进一步学习,掌握,最后感谢老师这个学期的指导与帮助

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- zicool.com 版权所有 湘ICP备2023022495号-2

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务