实验指导书回溯法.docx
《实验指导书回溯法.docx》由会员分享,可在线阅读,更多相关《实验指导书回溯法.docx(19页珍藏版)》请在冰豆网上搜索。
实验指导书回溯法
算法设计与分析
实验指导书
(计算机科学与技术系)
编写
兰州交通大学电子与信息工程学院
2018年3月
目录
实验5回溯法…………………………….........................................1
实验三回溯法
一、实验目的
(1)回溯法是一种通用的解题法,通过本次实验,掌握回溯法求解问题的一般步骤,掌握如何定义问题的解空间,并将解空间组织为解空间树并掌握如何在解空间树中进行深度优先搜索;
利用回溯法解问题时一般按以下三步骤:
1)定义问题的解空间;
2)确定易于搜索的解空间结构;
3)以深度优先策略搜索解空间,并在搜索过程中用剪枝函数避免无效搜索;
(2)通过实验掌握回溯法思想和方法;
(3)培养学生的动手能力。
二、实验仪器设备
(1)计算机;
(2)C++编译调试环境。
三、实验原理
掌握将算法转换为可运行程序的步骤。
四、实验内容及注意事项
(1)装载问题。
(2)0-1背包问题。
(3)TSP问题。
(4)八皇后问题。
五、实验步骤
5.1装载问题
(1)问题描述:
一批集装箱共n个要装上2艘载重量分别为c1和c2的轮船,其中集装箱i的重量为Wi且W1+W2+……+Wn<=c1+c2;试确定一个合理的装载方案使这n个集装箱装上这两艘轮船。
(2)求解思路:
装载问题如果有解,那么相当于将一艘轮船尽可能的装满,那么装载问题转换为一个特殊的(所有物品单位价值均为1)的0-1背包问题,解空间树为子集树。
回溯的过程可以使用约束函数(轮船1是否能装下选定的集装箱)以及限界函数(装入轮船1的集装箱重量之和+待选择的集装箱重量之和与当前最优值的对比)进行剪枝。
回溯的方法有递归回溯与迭代回溯。
具体代码如下:
#include
usingnamespacestd;
template
TMaxLoading(Tw[],Tc,intn,intbestx[]);//在GNU中,需提前声明
intcount=0;//用来记录递归回溯搜索了多少个节点
template
classLoading
{
private:
intn;//Thenumbers
int*x;//当前的搜索的路径x[i]==1表示在第i层进入了左子树
int*bestx;//当前已知的最优解
T*w;//Theweightarray
Tc;//Thefirstbox'sweight
Tcw;//currentweight
Tbestw;//Thebestweight
Tr;//Therestweight
public:
voidbacktrack(inti);
TfriendMaxLoading(Tw[],Tc,intn,intbestx[]);
};
template
voidLoading:
:
backtrack(inti)
{
count++;
if(i>n)
{
bestw=cw;//局部最优值
for(intj=1;j<=n;j++)
bestx[j]=x[j];//局部最优解
}
else
{
r-=w[i];//处理第i层的本节点
if(cw+w[i]<=c)//进入左子树
{
x[i]=1;
cw+=w[i];
backtrack(i+1);
cw-=w[i];//左子树搜索完毕,必须准备回溯
}
if(cw+r>=bestw)//进入右子树
{
x[i]=0;
backtrack(i+1);
}//右子树搜索完毕
r+=w[i];//两颗子树都搜索完毕,必须回溯
}
}
template
TMaxLoading(Tw[],Tc,intn,intbestx[])
{
/*
//递归回溯容易理解
LoadingX;
X.x=newint[n+1];//集装箱编号从1开始
X.bestx=bestx;//引用
X.w=w;
X.c=c;
X.n=n;
X.cw=0;
X.bestw=0;
X.r=0;//
for(inti=1;i<=n;i++)
X.r+=w[i];//r初始时为所有集装箱的重量之和
X.backtrack
(1);
delete[]X.x;
returnX.bestw;
*/
inti=1;
int*x=newint[n+1];
Tbestw=0,cw=0,r=0;
for(intj=1;j<=n;j++)
r+=w[j];
while(true)
{
while(i<=n&&cw+w[i]<=c)//深度优先只要能进入左子树,就进入左子树
{
r-=w[i];
cw+=w[i];
x[i]=1;
i++;
}//无法继续进入左子树
(1)i>n
(2)cw+w[i]>c
if(i>n){
bestw=cw;//局部最优值
for(intj=1;j<=n;j++)
bestx[j]=x[j];//局部最优解
}
else//表示由于约束函数的限制(cw+w[i]>c)而停止上次循环,无法进入下一层的左子树
//右子树肯定可以进入
{
r-=w[i];
x[i]=0;
i++;
}
while(cw+r<=bestw)
//如果前述循环已经得到了一个局部最优解和对应的局部最优值
//可以沿着右子树一路回溯剪枝
{
i--;
while(i>0&&x[i]==0)
{
r+=w[i];
i--;
}
if(i==0)
{
delete[]x;
returnbestw;
}
x[i]=0;
cw-=w[i];
i++;
}
}
}
intmain()
{
intw[]={0,16,15,15};//算法下标从1开始
intbestx[4];
intn=3;
intmax=0;
for(inti=1;i<=10000000;i++)//可以循环运行1000万次,对比递归回溯和迭代回溯的效率
max=MaxLoading(w,30,n,bestx);
cout<<"max="<cout<<"bestsolutionis:
"<for(inti=1;i<=n;i++)
cout<cout<cout<<"searchednodescount:
"<}
5.20-1背包问题
给定n种物品和一背包。
物品i的重量是wi,其价值为vi,背包的容量为C。
问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?
0-1背包问题是一个特殊的整数规划问题。
0-1背包问题的解空间树是子集树。
#include"stdafx.h"
#include
#include
usingnamespacestd;
typedefintTypew;
typedefintTypep;
//物品类
classObject{
friendclassKnap;
public:
intoperator<(Objecta)const{
return(d>=a.d);
}
private:
intID;//物品编号
Typeww;//物品重量
Typepp;//物品价值
doubled;//单位重量价值
};
//0-1背包问题的主类
classKnap{
public:
Knap(Typew*w,Typep*p,Typewc,intn);
TypepKnapsack();//回溯法解决0-1背包问题的主函数
voidBackTrack(intfloor);//递归回溯法
voidprint();//打印结果
private:
TypepBound(inti);//计算结点价值上界
Typewc;//背包容量
intn;//物品总数
Object*Q;//在Q数组中存放的物品以单位重量价值降序排序注意与装载问题的不同!
!
!
Typewcw;//当前装入背包的物品重量之和
Typepcp;//当前装入背包的物品价值之和
int*cbestx;//当前最优解
intcount;//最优解的个数
int*bestx[10];//最优解,最优解的个数不超过10个。
Typepbestp;//最优值
Typepoldbestp;//用于回溯法边界处理,保存上一次最优值
};
Knap:
:
Knap(Typew*w,Typep*p,Typewc,intn)
{
//初始化
TypewW=0;
TypepP=0;
count=0;
this->c=c;
oldbestp=0;
this->n=n;
cw=0;
cp=0;
Q=newObject[n];
for(inti=0;i{
Q[i].ID=i+1;
Q[i].d=1.0*p[i]/w[i];
Q[i].w=w[i];
Q[i].p=p[i];
P+=p[i];
W+=w[i];
}
//所有物品的总重量小于等于背包容量c,全部物品装入背包,程序可以结束。
if(W<=c)
{
bestp=P;
int*newbestx=newint[n];
for(inti=0;i{
newbestx[i]=1;
}
bestx[count++]=newbestx;
}
//所有物品的总重量大于背包容量c,存在最佳装包方案
sort(Q,Q+n);//采用stl的sort,Q重载了<运算符,默认升序排序,所以运算符重载取反了。
}
TypepKnap:
:
Knapsack()
{
if(count>0)//背包容量足够大,在初始化时已经将所有物品装入背包
{
print();
returnbestp;
}
else//背包容量小于物品所有重量,存在最优装包方案
{
cbestx=newint[n];
BackTrack(0);//从数组Q下标0,首结点开始回溯法求解
}
}
voidKnap:
:
BackTrack(intfloor)
{
if(floor>n-1)//已经到了子集树中的叶子结点
{
if(cp==oldbestp)//说明可能有多个最优解
{
int*newbe=newint[n];
for(inti=0;i{
newbe[i]=cbestx[i];
}
bestx[count++]=newbe;
}
if(cp>oldbestp)//说明最优解需要更新同时只有一个
{
count=0;
int*newbe=newint[n];
for(inti=0;i{
newbe[i]=cbestx[i];
}
bestx[count++]=newbe;
oldbestp=cp;
}
}
else
{
//选取数组Q下标为floor的物品,满足背包容量约束
if(c>=cw+Q[floor].w)
{
cw+=Q[floor].w;
cp+=Q[floor].p;
if(cp>=bestp)
bestp=cp;
cbestx[floor]=1;
BackTrack(floor+1);
cw-=Q[floor].w;
cp-=Q[floor].p;
}
//舍去数组Q下标为floor的物品,满足限界函数
if(cp+Bound(floor+1)>=bestp)
{
cbestx[floor]=0;
BackTrack(floor+1);
}
}
}
voidKnap:
:
print()
{
Typep*original=newint[n+1];
cout<<"以下每行为一种解法:
"<for(inti=count-1;i>=0;i--)
{
for(intj=0;j{
original[Q[j].ID]=bestx[i][j];
}
for(intk=1;k<=n;k++)
{
cout<}
cout<}
cout<<"最优解的个数:
"<cout<<"最优值:
"<}
TypepKnap:
:
Bound(inti)
{
Typewcleft=c-cw;
Typepb=cp;
while(i{
cleft-=Q[i].w;
b+=Q[i].p;
i++;
}
if(ireturnb;
}
int_tmain(intargc,_TCHAR*argv[])
{
constintN=4;
Typewc=7;
Typeww[N]={2,3,5,2};
Typepp[N]={6,4,8,4};
cout<<"背包容量:
"<"<cout<<"物品重量数组:
";
for(inti=0;i{
cout<}
cout<cout<<"物品价值数组:
";
for(inti=0;i{
cout<
}
cout<KnapK(w,p,c,N);
K.Knapsack();
K.print();
system("pause");
return0;
}
5.3TSP问题
假设有四个城市A,B,C,D,从A出发,求经过所有顶点后回到A的最短路径,我们用树表示所有的解,他的解空间树是一个排列树,如下图所示:
//5d9旅行员售货问题回溯法求解
#include"stdafx.h"
#include
usingnamespacestd;
constintN=4;//图的顶点数
template
classTraveling
{
template
friendTypeTSP(Typea[N+1][N+1],intn);
private:
voidBacktrack(inti);
intn,//图G的顶点数
*x,//当前解
*bestx;//当前最优解
Type(*a)[N+1],//图G的领接矩阵
cc,//当前费用
bestc;//当前最优值
intNoEdge;//无边标记
};
template
inlinevoidSwap(Type&a,Type&b);
template
TypeTSP(Typea[N+1][N+1],intn);
intmain()
{
cout<<"图的顶点个数n="<inta[N+1][N+1]={{0,0,0,0,0},{0,0,30,6,4},{0,30,0,5,10},{0,6,5,0,20},{0,4,10,20,0}};
cout<<"图的邻接矩阵为:
"<for(inti=1;i<=N;i++)
{
for(intj=1;j<=N;j++)
{
cout<}
cout<}
cout<<"最短回路的长为:
"<system("pause");
return0;
}
template
voidTraveling:
:
Backtrack(inti)
{
if(i==n)
{
if(a[x[n-1]][x[n]]!
=0&&a[x[n]][1]!
=0&&
(cc+a[x[n-1]][x[n]]+a[x[n]][1]{
for(intj=1;j<=n;j++)bestx[j]=x[j];
bestc=cc+a[x[n-1]][x[n]]+a[x[n]][1];
}
}
else
{
for(intj=i;j<=n;j++)
{
//是否可进入x[j]子树?
if(a[x[i-1]][x[j]]!
=0&&(cc+a[x[i-1]][x[i]]{
//搜索子树
Swap(x[i],x[j]);
cc+=a[x[i-1]][x[i]];//当前费用累加
Backtrack(i+1);//排列向右扩展,排列树向下一层扩展
cc-=a[x[i-1]][x[i]];
Swap(x[i],x[j]);
}
}
}
}
template
TypeTSP(Typea[N+1][N+1],intn)
{
TravelingY;
Y.n=n;
Y.x=newint[n+1];
Y.bestx=newint[n+1];
for(inti=1;i<=n;i++)
{
Y.x[i]=i;
}
Y.a=a;
Y.cc=0;
Y.bestc=0;
Y.NoEdge=0;
Y.Backtrack
(2);
cout<<"最短回路为:
"<for(inti=1;i<=n;i++)
{
cout<";
}
cout<delete[]Y.x;
Y.x=0;
delete[]Y.bestx;
Y.bestx=0;
returnY.bestc;
}
template
inlinevoidSwap(Type&a,Type&b)
{
Typetemp=a;
a=b;
b=temp;
}
5.4八皇后问题
八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。
该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:
在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
高斯认为有76种方案。
1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。
计算机发明后,有多种计算机语言可以解决此问题。
#include
#include
usingnamespacestd;
#defineN8
intx[N+1];
intsum=0;
intcount=0;
boolPlace(intk)
{
for(intj=1;jif((abs(k-j)==abs(x[k]-x[j]))||(x[j]==x[k]))
returnfalse;
returntrue;
}
voidBacktrack(intt)
{
count++;
if(t>N)
{
sum++;
for(intj=1;j<=N;j++)
cout<cout<}
else
{
for(inti=1;i<=N;i++)
{
x[t]=i;
if(Place(t))
Backtrack(t+1);
}
}
}
intmain()
{
Backtrack
(1);
cout<<"sum="<inttotal=1<<24;
cout<<"total="<cout<<"count="<cout<<"ratio="<return