0-1背包问题计科1班朱润华2012040732方法1:回溯法 一、回溯法描述:用回溯法解问题时, 应明确定义问题的解空间。
问题的解空间至少包含问题的一个(最优)解。
对于0-1背包问题,解空间由长度为n 的0-1向量组成。
该解空间包含对变量的所有 0-1 赋值。
例如 n=3 时,解空间为: {(0, 0, 0), (0, 1, 0), (0, 0, 1) , (1, 0, 0),(0, 1, 1), (1, 0, 1), (1, 1, 0), (1 , 1, 1) 然后可将解空间组织成树或图的形式, 0-1背包则可用完全二叉树表示其解空间给定n 种物品和一背包。
物品i 的重量是wi ,其价值为vi ,背包的容量为 C 。
问:应如何选择装入背包的物品,使得装入背包中物品的总价值 最大?形式化描述:给定 c >0, wi >0, vi >0 , 1 w i < n.要求找一 n 元向量(x1,x2,…,xn,),xi € {0,1}, ? 刀wi xi w c,且刀vi xi 达最大.即一个特殊的整数规划问题。
二、回溯法步骤思想描述:0-1背包问题是子集选取问题。
0-1背包问题的解空间可以用子集树表示。
在搜索解空 间树时,只要其左儿子节点是一个可行节点,搜索就进入左子树。
当右子树中有可能含有最优解时,才进入右子树搜索。
否则,将右子树剪去。
设 r 是当前剩余物品价值总和,cp 是当前价值;bestp 是当前最优价值。
当cp+r<=bestp 时,可剪去右子树。
计算右子树上界的更好的方法是将剩余物品依次按其单位价值排序, 然后依次装入物品,直至装不下时,再装入物品一部分而装满背包。
例如:对于 0-1 背包问题的一个实例,n=4,c=7,p=[9,10,7,4],w=[3,5,2,1] 品的单位重量价值分别为[3,2,3,5,4]。
以物品单位重量价值的递减序装入物品。
品4,然后装入物品3和1.装入这3个物品后,剩余的背包容量为1,只能装由此得一个解为[1,0.2,1,1],其相应价值为22。
尽管这不是一个可行解,但可以证明其价 值是最优值的上界。
因此,对于这个实例,最优值不超过在实现时,由 Bound 计算当前节点处的上界。
类Knap 的数据成员记录解空间树中的节点信息,以减少参数传递调用所需要的栈空间。
在解空间树的当前扩展节点处, 仅要进入右子树时才计算上界 Bound,以判断是否可将右子树剪去。
进入左子树时不需要计算上界,因 为上界预期父节点的上界相同。
三、回溯法实现代码:#i nclude "stdafx.h" #in clude <iostream> using n ames pace std;temp late<class Typ ew,class Typep> class Knap {temp latevciass Typ ew,class Typep>friend Typep Knap sack(T ypep [],T ypew [],T yp ew,i nt); private: Typep Boun d(i nt i);。
这4个物先装入物0.2的物品2。
22。
void Backtrack(int i);Typew c; // 背包容量int n; // 物品数Typew *w; // 物品重量数组Typep *p; // 物品价值数组Typew cw; // 当前重量Typep cp; // 当前价值Typep bestp;// 当前最后价值}; template<class Typew,class Typep> Typep Knapsack(Typep p[],Typew w[],Typew c,int n); template <class Type>inline void Swap(Type &a,Type &b); template<class Type> void BubbleSort(Type a[],int n); int main() { int n = 4;//int c = 7;//物品数背包容量int p[] = {0,9,10,7,4};// int w[] = {0,3,5,2,1};//cout<<" 背包容量为:物品价值下标从1 开始物品重量下标从1 开始"<<c<<endl;cout<<" 物品重量和价值分别为:"<<endl;for(int i=1; i<=n; i++){cout<<"("<<w[i]<<","<<p[i]<<") ";}cout<<endl;cout<<" 背包能装下的最大价值为:"<<Knapsack(p,w,c,n)<<endl; return 0;}template<class Typew,class Typep>void Knap<Typew,Typep>::Backtrack(int i){if(i>n)//{bestp = cp;return;}到达叶子节点if(cw + w[i] <= c)//{cw += w[i];进入左子树// 以物品单位重量价值递减序装入物品 while (i <= n && w[i] <= cleft){cleft -= w[i]; b += p[i]; i++;// if (i <= {b += p[i]/w[i] * cleft; } return b;}class Object{template<class Typew,class Typep>friend Typep Knapsack(Typep[],Typew [],Typew,int); public:int operator <= (Object a)const { return (d>=a.d); }private: int ID; float d;cp += p[i]; Backtrack(i+1); cw -= w[i]; cp -= p[i];}if(Bound(i+1)>bestp)// {Backtrack(i+1);} } template<class Typew, class Typep> Typep Knap<Typew, Typep>::Bound(int i)// { Typew cleft = c - cw; // Typep b = cp; 进入右子树 计算上界剩余容量 装满背包n)}; template<class Typew,class Typep>Typep Knapsack(Typep p[],Typew w[],Typew c,int n) { // 为Knap::Backtrack 初始化Typew W = 0;Typep P = 0;Object *Q = new Object[n]; for(int i=1; i<=n; i++) { Q[i-1].ID = i; Q[i-1].d = 1.0 * p[i]/w[i]; P += p[i];W += w[i]; } if(W <= c)// {return P;}装入所有物品// 依物品单位重量价值排序BubbleSort(Q,n);Knap<Typew,Typep> K;K.p = new Typep[n+1];K.w = new Typew[n+1];for(int i=1; i<=n; i++){K.p[i] = p[Q[i-1].ID];K.w[i] = w[Q[i-1].ID]; }K.cp = 0;K.cw = 0; K.c = c;K.n = n; K.bestp = 0;// 回溯搜索K.Backtrack(1);delete []Q;delete []K.w;delete []K .p; return K.best p;} temp latevciass Type〉void BubbleSort(Ty pe a[],i nt n){// 记录一次遍历中是否有元素的交换bool excha nge;for(i nt i=0; i<n-1;i++){excha nge = false ;for(i nt j=i+1; j<=n-1; j++){if(a[j]<=a[j-1]){Swa p( a[j],a[j-1]); excha nge =true;}//如果这次遍历没有元素的交换,那么排序结束if(false == excha nge) {break ;}}}temp late <class Type〉in li ne void Swap(Type &a,T ype &b) {Type temp = a;a = b;b = temp;}四、程序运行结果:五、回溯法解决 0-1 背包问题复杂度分析:计算上界需要0(n)时间,在最坏情况下有0(2人n)个右儿子节点需要计算上界,故解0-1 背包问题的回溯算法所需要的计算时间为 0(n^n)。
方法 2:分支限界法: 一、分支限界法描述:给定n 种物品和一背包。
物品i 的重量是wi ,其价值为Vi ,背包的容量为 Co 问:应如 何选择装入背包的物品,使得装入背包中物品的总价值最大 ?形式化描述:给定 c >0, wi >0, vi >0 , 1 w i w n.要求找一 n 元向量(x1,x2,…,xn,), xi € {0,1}, ?刀wi xi w c,且刀vi xi 达最大.即一个特殊的整数规划问题。
二、分支限界法步骤思想:首先, 要对输入数据进行预处理, 将各物品依其单位重量价值从大到小进行排列。
先队列分支限界法中, 节点的优先级由已装袋的物品价值加上剩下的最大单位重量价值的物 品装满剩余容量的价值和。
算法首先检查当前扩展结点的左儿子结点的可行性。
如果该左儿子结点是可行结点, 将它加入到子集树和活结点优先队列中。
当前扩展结点的右儿子结点一定是可行结点, 右儿子结点满足上界约束时才将它加入子集树和活结点优先队列。