回溯法和分支限界法解决01背包题.docx
《回溯法和分支限界法解决01背包题.docx》由会员分享,可在线阅读,更多相关《回溯法和分支限界法解决01背包题.docx(18页珍藏版)》请在冰豆网上搜索。
回溯法和分支限界法解决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,1≤i≤n.要求找一n元向量(x1,x2,…,xn,),xi∈{0,1},?
∑wixi≤c,且∑vixi达最大.即一个特殊的整数规划问题。
二、回溯法步骤思想描述:
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]。
这4个物品的单位重量价值分别为[3,2,3,5,4]。
以物品单位重量价值的递减序装入物品。
先装入物品4,然后装入物品3和1.装入这3个物品后,剩余的背包容量为1,只能装0.2的物品2。
由此得一个解为[1,0.2,1,1],其相应价值为22。
尽管这不是一个可行解,但可以证明其价值是最优值的上界。
因此,对于这个实例,最优值不超过22。
在实现时,由Bound计算当前节点处的上界。
类Knap的数据成员记录解空间树中的节点信息,以减少参数传递调用所需要的栈空间。
在解空间树的当前扩展节点处,仅要进入右子树时才计算上界Bound,以判断是否可将右子树剪去。
进入左子树时不需要计算上界,因为上界预期父节点的上界相同。
三、回溯法实现代码:
#include"stdafx.h"
#include
usingnamespacestd;
template
classKnap
{
template
friendTypepKnapsack(Typep[],Typew[],Typew,int);
private:
TypepBound(inti);
voidBacktrack(inti);
Typewc;//背包容量
intn;//物品数
Typew*w;//物品重量数组
Typep*p;//物品价值数组
Typewcw;//当前重量
Typepcp;//当前价值
Typepbestp;//当前最后价值
};
template
TypepKnapsack(Typepp[],Typeww[],Typewc,intn);
template
inlinevoidSwap(Type&a,Type&b);
template
voidBubbleSort(Typea[],intn);
intmain()
{
intn=4;//物品数
intc=7;//背包容量
intp[]={0,9,10,7,4};//物品价值下标从1开始
intw[]={0,3,5,2,1};//物品重量下标从1开始
cout<<"背包容量为:
"<cout<<"物品重量和价值分别为:
"<for(inti=1;i<=n;i++)
{
cout<<"("<}
cout<cout<<"背包能装下的最大价值为:
"<return0;
}
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);
}
}
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;
}
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;
}
template
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;
}
}
}
template
inlinevoidSwap(Type&a,Type&b)
{
Typetemp=a;
a=b;
b=temp;
}
四、程序运行结果:
五、回溯法解决0-1背包问题复杂度分析:
计算上界需要O(n)时间,在最坏情况下有O(2^n)个右儿子节点需要计算上界,故解0-1背包问题的回溯算法所需要的计算时间为O(n2^n)。
方法2:
分支限界法:
一、分支限界法描述:
给定n种物品和一背包。
物品i的重量是wi,其价值为vi,背包的容量为C。
问:
应如何选择装入背包的物品,使得装入背包中物品的总价值最大?
形式化描述:
给定c>0,wi>0,vi>0,1≤i≤n.要求找一n元向量(x1,x2,…,xn,),xi∈{0,1},∋∑wixi≤c,且∑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,O}—>{O}—>{}。
三、分支限界法解决0-1背包问题实现代码:
//0-1背包问题分支限界法求解
#include"stdafx.h"
#include"MaxHeap.h"
#include
usingnamespacestd;
classObject
{
template
friendTypepKnapsack(Typepp[],Typeww[],Typewc,intn,intbestx[]);
public:
intoperator<=(Objecta)const
{
returnd>=a.d;
}
private:
intID;
floatd;//单位重量价值
};
templateclassKnap;
classbbnode
{
friendKnap;
template
friendTypepKnapsack(Typepp[],Typeww[],Typewc,intn,intbestx[]);
private:
bbnode*parent;//指向父节点的指针
boolLChild;//左儿子节点标识
};
template
classHeapNode
{
friendKnap;
public:
operatorTypep()const
{
returnuprofit;
}
private:
Typepuprofit,//节点的价值上界
profit;//节点所相应的价值
Typewweight;//节点所相应的重量
intlevel;//活节点在子集树中所处的层序号
bbnode*ptr;//指向活节点在子集中相应节点的指针
};
template
classKnap
{
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;//最优解
};
template
inlinevoidSwap(Type&a,Type&b);
template
voidBubbleSort(Typea[],intn);
intmain()
{
intn=3;//物品数
intc=30;//背包容量
intp[]={0,45,25,25};//物品价值下标从1开始
intw[]={0,16,15,15};//物品重量下标从1开始
intbestx[4];//最优解
cout<<"背包容量为:
"<cout<<"物品重量和价值分别为:
"<for(inti=1;i<=n;i++)
{
cout<<"("<}
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);
}
//优先队列式分支限界法,返回最大价值,bestx返回最优解
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;
}
template
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;
}
}
}
template
inlinevoidSwap(Type&a,Type&b)
{
Typetemp=a;
a=b;
b=temp;
}
四、程序运行结果:
五、分支限界法解决0-1背包问题复杂度分析:
时间复杂度为:
O(2^n);空间复杂度:
O(n2^n)。
六、回溯法与分支限界法分析比较:
这两种算法都得到了验证,运行结果证明了算法设计是