回溯法和分支限界法解决01背包题.docx
《回溯法和分支限界法解决01背包题.docx》由会员分享,可在线阅读,更多相关《回溯法和分支限界法解决01背包题.docx(19页珍藏版)》请在冰豆网上搜索。
回溯法和分支限界法解决01背包题
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,1wixi€{0,1},?
刀wixiwc,且刀vixi达最大.即一个特殊的整数规划问题。
二、回溯法步骤思想描述:
0-1背包问题是子集选取问题。
0-1背包问题的解空间可以用子集树表示。
在搜索解空间树时,只要其左儿子节点是一个可行节点,搜索就进入左子树。
当右子树中有可能含有最
优解时,才进入右子树搜索。
否则,将右子树剪去。
设r是当前剩余物品价值总和,cp是
当前价值;bestp是当前最优价值。
当cp+r<=bestp时,可剪去右子树。
计算右子树上界的
更好的方法是将剩余物品依次按其单位价值排序,然后依次装入物品,直至装不下时,再装
入物品一部分而装满背包。
。
这4个物先装入物
0.2的物品2。
例如:
对于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,只能装
22。
由此得一个解为[1,0.2,1,1],其相应价值为22。
尽管这不是一个可行解,但可以证明其价值是最优值的上界。
因此,对于这个实例,最优值不超过
在实现时,由Bound计算当前节点处的上界。
类Knap的数据成员记录解空间树中的节
点信息,以减少参数传递调用所需要的栈空间。
在解空间树的当前扩展节点处,仅要进入右
子树时才计算上界Bound,以判断是否可将右子树剪去。
进入左子树时不需要计算上界,因
为上界预期父节点的上界相同。
三、回溯法实现代码:
#include"stdafx.h"
#include
usingnamespacestd;
template
classKnap
{
templatevciassTypew,classTypep>
friendTypepKnapsack(Typep[],Typew[],Typew,int);private:
TypepBound(inti);
voidBacktrack(inti);
Typewc;
//
背包容量
intn;//
物品数
Typew*w;
//
物品重量数组
Typep*p;
//
物品价值数组
Typewcw;
//
当前重量
Typepcp;
//
当前价值
Typepbestp;//
当前最后价值
};templateTypepKnapsack(Typepp[],Typeww[],Typewc,intn);template
inlinevoidSwap(Type&a,Type&b);templatevoidBubbleSort(Typea[],intn);intmain(){
intn=4;//
intc=7;//
物品数背包容量
intp[]={0,9,10,7,4};//intw[]={0,3,5,2,1};//cout<<"背包容量为:
物品价值下标从1开始物品重量下标从1开始"<cout<<"物品重量和价值分别为:
"<for(inti=1;i<=n;i++)
{
cout<<"("<}
cout<cout<<"背包能装下的最大价值为:
"<}
template
voidKnap:
:
Backtrack(inti)
{
if(i>n)//
{
bestp=cp;
return;
}
到达叶子节点
if(cw+w[i]<=c)//{
cw+=w[i];
进入左子树
cp+=p[i];Backtrack(i+1);cw-=w[i];cp-=p[i];
}
if(Bound(i+1)>bestp)//
{
Backtrack(i+1);
}}templateTypepKnap:
:
Bound(inti)//{
Typewcleft=c-cw;//
Typepb=cp;
进入右子树
计算上界
剩余容量
//以物品单位重量价值递减序装入物品while(i<=n&&w[i]<=cleft)
{
cleft-=w[i];b+=p[i];i++;
装满背包
n)
//
if(i<=
{
b+=p[i]/w[i]*cleft;
}returnb;
}
classObject
{
template
friendTypepKnapsack(Typep[],Typew[],Typew,int);public:
intoperator<=(Objecta)const{
return(d>=a.d);
}
private:
intID;floatd;
};template
TypepKnapsack(Typepp[],Typeww[],Typewc,intn){
//为Knap:
:
Backtrack初始化TypewW=0;
TypepP=0;
Object*Q=newObject[n];for(inti=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)//{
returnP;
}
//依物品单位重量价值排序BubbleSort(Q,n);
KnapK;
K.p=newTypep[n+1];
K.w=newTypew[n+1];
for(inti=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;returnK.bestp;
}templatevciassType〉
voidBubbleSort(Typea[],intn)
{
//记录一次遍历中是否有元素的交换
boolexchange;
for(inti=0;i{
exchange=false;
for(intj=i+1;j<=n-1;j++)
{
if(a[j]<=a[j-1])
{
Swap(a[j],a[j-1]);exchange=true;
如果这次遍历没有元素的交换,那么排序结束
}
//
if(false==exchange){
break;
}
}
}
templateinlinevoidSwap(Type&a,Type&b){
Typetemp=a;
a=b;
b=temp;
}
四、程序运行结果:
背包谷重方.7
物品S量和祚值分别为:
<3,9><5,10><2,7〉<1,4〉
犬价值为:
20蓿按准意键蜒续■..
五、回溯法解决0-1背包问题复杂度分析:
计算上界需要0(n)时间,在最坏情况下有0(2人n)个右儿子节点需要计算上界,故解0-1背包问题的回溯算法所需要的计算时间为0(n^n)。
方法2:
分支限界法:
一、分支限界法描述:
给定n种物品和一背包。
物品i的重量是wi,其价值为Vi,背包的容量为Co问:
应如何选择装入背包的物品,使得装入背包中物品的总价值最大?
形式化描述:
给定c>0,wi>0,vi>0,1wiwn.要求找一n元向量(x1,x2,…,xn,),xi€{0,1},?
刀wixiwc,且刀vixi达最大.即一个特殊的整数规划问题。
二、分支限界法步骤思想:
在优
则仅当
首先,要对输入数据进行预处理,将各物品依其单位重量价值从大到小进行排列。
先队列分支限界法中,节点的优先级由已装袋的物品价值加上剩下的最大单位重量价值的物品装满剩余容量的价值和。
算法首先检查当前扩展结点的左儿子结点的可行性。
如果该左儿子结点是可行结点,将它加入到子集树和活结点优先队列中。
当前扩展结点的右儿子结点一定是可行结点,
右儿子结点满足上界约束时才将它加入子集树和活结点优先队列。
当扩展到叶节点时为问题
的最优值。
例如:
0-1背包问题,当n=3时,w={16,15,15},p={45,25,25},c=30。
优先队列式分支限界法:
处理法则:
价值大者优先。
{}—>{A}—>{B,C}—>{C,D,E}—>{C,E}—>{C,J,K}—>{C}—>{F,G}—>{G,L,M}—>{G,M}—>{G}—>{N,0}—>{0}—>{}。
三、分支限界法解决0-1背包问题实现代码:
//0-1背包问题分支限界法求解#include"stdafx.h"#include"MaxHeap.h"
#includeusingnamespacestd;
class0bject
{
template
friendTypepKnapsack(Typepp[],Typeww[],Typewc,intn,intbestx[]);public:
intoperator<=(0bjecta)const
{
returnd>=a.d;
}
private:
intID;
单位重量价值
floatd;//
};
templateclassKnap;classbbnode
{
friendKnap;
template
friendTypepKnapsack(Typepp[],Typeww[],Typewc,intn,intbestx[]);private:
bbnode*parent;//boolLChild;//
};
templateclassHeapNode
{
friendKnap;
public:
operatorTypep()const
{
returnuprofit;
}
private:
Typepuprofit,profit;//
Typewweight;
intlevel;//bbnode*ptr;
};
指向父节点的指针
左儿子节点标识
//
//
//
节点的价值上界
节点所相应的价值
节点所相应的重量活节点在子集树中所处的层序号指向活节点在子集中相应节点的指针
templateclassKnap
{
template
friendTypepKnapsack(Typepp[],Typeww[],Typewc,intn,intbestx[]);public:
TypepMaxKnapsack();
private:
MaxHeap>*H;
TypepBound(inti);
voidAddLiveNode(Typepup,Typepcp,Typewcw,boolch,intlev);
bbnode*E;//Typewc;
intn;//
//
指向扩展节点的指针背包容量物品数
Typew*w;
Typep*p;
Typewcw;
//
//
//
物品重量数组物品价值数组当前重量
当前价值
最优解
Typepcp;//int*bestx;//
};
templateinlinevoidSwap(Type&a,Type&b);templatevoidBubbleSort(Typea[],intn);intmain()
{
物品数
背包容量
intn=3;//
intc=30;//
物品价值下标从1开始
物品重量下标从1开始
intp[]={0,45,25,25};//
intw[]={0,16,15,15};//
intbestx[4];//最优解
cout<<"背包容量为:
"<"<for(inti=1;i<=n;i++)
{cout<<"("<}
cout<cout<<"背包能装下的最大价值为:
"<"<for(inti=1;i<=n;i++)
{cout<}
cout<return0;
}
template
计算节点所相应价值的上界
TypepKnap:
:
Bound(inti)//
{
Typewcleft=c-cw;//剩余容量高
Typepb=cp;//价值上界
//以物品单位重量价值递减序装填剩余容量
while(i<=n&&w[i]<=cleft)
{
cleft-=w[i];
b+=p[i];
i++;
}
//装填剩余容量装满背包if(i<=n)
{
b+=p[i]/w[i]*cleft;
}
returnb;
}
//将一个活节点插入到子集树和优先队列中template
voidKnap:
:
AddLiveNode(Typepup,Typepcp,Typewcw,boolch,intlev){
bbnode*b=newbbnode;b->parent=E;
b->LChild=ch;
HeapNodeN;
N.uprofit=up;
N.profit=cp;
N.weight=cw;
N.level=lev;
N.ptr=b;
H->Insert(N);
}
//template
TypepKnap:
:
MaxKnapsack(){
H=newMaxHeap>(1000);
//为bestx分配存储空间bestx=newint[n+1];
//初始化inti=1;
E=0;
cw=cp=0;
Typepbestp=0;//当前最优值
Typepup=Bound
(1);//价值上界
//搜索子集空间树
while(i!
=n+1)
//检查当前扩展节点的左儿子节点Typewwt=cw+w[i];
if(wt<=c)//左儿子节点为可行节点
{
if(cp+p[i]>bestp)
{
bestp=cp+p[i];
}
AddLiveNode(up,cp+p[i],cw+w[i],true,i+1);}
up=Bound(i+1);
//检查当前扩展节点的右儿子节点if(up>=bestp)//右子树可能含有最优解{
AddLiveNode(up,cp,cw,false,i+1);
}
//取下一扩展节点HeapNodeN;H->DeleteMax(N);
E=N.ptr;cw=N.weight;cp=N.profit;up=N.uprofit;
}
//
i=N.level;
构造当前最优解
for(intj=n;j>0;j--)
{bestx[j]=E->LChild;E=E->parent;
}
returncp;
}
//返回最大价值,bestx返回最优解template
TypepKnapsack(Typepp[],Typeww[],Typewc,intn,intbestx[]){
装包物品重量
装包物品价值
//初始化TypewW=0;//TypepP=0;//
//定义依单位重量价值排序的物品数组Object*Q=newObject[n];
for(inti=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){returnP;//
}
//依单位价值重量排序BubbleSort(Q,n);
//创建类Knap的数据成员KnapK;
K.p=newTypep[n+1];K.w=newTypew[n+1];for(inti=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;
//调用MaxKnapsack求问题的最优解Typepbestp=K.MaxKnapsack();for(intj=1;j<=n;j++)
{bestx[Q[j-1].ID]=K.bestx[j];
}
deleteQ;
delete[]K.w;
delete[]K.p;
delete[]K.bestx;returnbestp;
}templatevoidBubbleSort(Typea[],intn)
{
//记录一次遍历中是否有元素的交换
boolexchange;
for(inti=0;i{
exchange=false;
for(intj=i+1;j<=n-1;j++)
{
if(a[j]<=a[j-1])
{
Swap(a[j],a[j-1]);exchange=true;
如果这次遍历没有元素的交换,那么排序结束
}
//
if(false==exchange)
{
break;
}
}
}templateinlinevoidSwap(Type&a,Type&b){
Typetemp=a;
a=b;
b=temp;
}
四、程序运行结果:
30
量量和价值分别为:
45〉<15,25〉<15.25〉
犬价<为=50
Q问题最优解为=
011
请按任意键继续.••
五、分支限界法解决0-1背包问题复杂度分析:
时间复杂度为:
0(2人n);空间复杂度:
0525)。
六、回溯法与分支限界法分析比较:
这两种算法都得到了验证,运行结果证明了算法设计是可行的。
通过对O-1背包问题的算法设计及时间复杂度分析可以看出:
无论采用回溯法还是分支限界法,都是在已知约束条件下求解最大值建立数学模型算法实现的过程;但算法具体实现和数据结构的建立要用到递归和栈操作。
比较回溯法和分支限界法,前者的时间复杂度高于后者,从耗费上而言优于后者。
对于回溯法,能够获得最优解,时间复杂度较高,判断右子树时,按效率密度vi/wi对剩余对象排序;对于分支限界法:
速度较快,易求解,不过占用的内存较大,效率不高。
成绩
成绩单