//删除动态内存分配
delete[]Y.x;
Y.x=0;
delete[]Y.bestx;
Y.bestx=0;
returnY.bestc;
}
/*=================================================================
Swap函数实现两个数值交换。
需要注意的是形参那里一定要使用&符号,
不然的话修改之后的结果不会在其他函数中看到。
=================================================================*/
template
inlinevoidSwap(Type&a,Type&b)
{
Typetemp=a;
a=b;
b=temp;
}
测试结果
1.使用的图如下所示:
2.相应的排列树如下所示:
3.至个叶结点的路径
叶结点
路径长度
路径顺序
L
59
1-->2-->3-->4-->1
M
66
1-->2-->4-->3-->1
N
25
1-->3-->2-->4-->1
O
66
1-->3-->4-->2-->1
P
25
1-->4-->2-->3-->1
Q
59
1-->4-->3-->2-->1
4.由上表可以知道最短的为至叶结点Q的路径1-->3-->2-->4-->1,长度为25。
这里可能会有疑问,至结点P的距离也为25,为什么不选择路径1-->4-->2-->3-->1。
这是因为只有当求得的路径比当前最优值小的时候才会记录,这里一样大,所以不会记录,也就不会输出这条路径。
5.算法输出结果如下:
6.可以看到输出的结果与分析的结果一样,所以算法实现正确。
并且可以看到分支限界法在实现我们给的这个图的时候,时间性能很好。
实验心得
因为我自己觉得解空间为排列树的问题的算法不是很好理解,所以就多编写了旅行售货员问题的代码,这里使用的是回溯法实现。
就整体上来说,我认为回溯法的思想还是很好理解的,就是扩展一个结点的子结点,继续扩展子结点的子结点,当不能扩展的时候,就返回到上一个扩展结点,找其他的子结点进行扩展,当往回找到根结点,根结点没有子结点继续扩展的时候,就结束算法。
但是这里是排列树的回溯法,就难理解一点。
主要是使用了交换。
voidbacktrack(intt)
{
if(t>n)output(x);
else
for(inti=t;i<=n;i++){
swap(x[t],x[i]);
if(legal(t))backtrack(t+1);
swap(x[t],x[i]);
}
}
排列树的实现的伪代码如上所示。
主要存在两个交换。
第一个交换是保存x[t]当前的状态,而后面一个交换则是回到x[t]刚才的状态。
理解了这一点之后,回溯法求解旅行售货员问题的代码其实也就不难理解了。
因为这里给的图的顶点比较少,无法直观的看出算法的时间性能。
就回溯法求解旅行售货员问题,如果不考虑更新bestx所需的计算时间,则Backtrack需要O((n-1)!
)计算时间。
由于算法Backtrack在最坏情况下可能需要更新当前最优解O((n-1)!
)次,每次更新bestx需要O(n)计算时间,所以整个算法的计算时间复杂性为O(n!
)。
通过这次实验,编写了回溯法求解旅行售货员问题的代码,熟悉了回溯法求解问题的步骤,熟练掌握了回溯法,理解了排列树的问题,进一步熟悉了旅行售货员问题的问题描述与解题思路。
相信在以后的学习工作中,一定可以熟练使用回溯法求解问题,当然肯定可以使用回溯法求解旅行售货员问题。
实验得分
助教签名
附录:
完整代码
#include
#include
#include
#include
#include
usingnamespacestd;
//定义图的顶点数
constintN=4;
/*============================================================================
定义Traveling类来存储的信息。
============================================================================*/
template
classTraveling
{
template
friendTTSP(T**a,intn);
private:
voidBacktrack(inti);
intn,//图G的顶点数
*x,//当前解
*bestx;//当前最优解
Type**a,//图G的领接矩阵
cc,//当前费用
bestc;//当前最优值
intNoEdge;//无边标记
};
/*============================================================================
Backtrack函数为递归算法。
当i=n时,当前扩展结点是排列树的叶结点的父结点。
此时算法检测图G是否存在一条
从顶点x[n-1]到顶点x[n]的边和一条从顶点x[n]到顶点1的边。
如果这两条边都存在,则
找到一条旅行售货员回路。
此时,算法还需要判断这条回路的费用是否优于已找到的当前
最优回路的费用bestc。
如果是,则必须更新当前最优值bestc和当前最优解bestx。
当i图G中存在从顶点x[i-1]到顶点x[i]
的边时,x[1:
i]构成图G的一条路径,且当x[1:
i]的费用小于当前最优值时算法进入排列
树的第i层,否则将减去相应的子树。
算法中用变量cc记录当前路径x[1:
i]的费用。
============================================================================*/
template
voidTraveling:
:
Backtrack(inti)
{
//前扩展结点是排列树的叶结点的父结点
if(i==n)
{
//检测是否存在一条从顶点x[n-1]到顶点x[n]的边和一条从顶点x[n]到顶点1的边
//存在且这条回路的费用小于已找到的最优费用
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];
}
}
//前扩展结点位于排列树的第i-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]]{
//搜索子树
//交换
inttemp=x[i];
x[i]=x[j];
x[j]=temp;
//当前费用累加
cc+=a[x[i-1]][x[i]];
//排列向右扩展,排列树向下一层扩展
Backtrack(i+1);
//当前费用减少
cc-=a[x[i-1]][x[i]];
//交换
temp=x[i];
x[i]=x[j];
x[j]=temp;
}
}
}
}
/*============================================================================
TSP函数进行初始化,并返回最短路径的长度。
主要为使用Backtrack函数进行搜索。
============================================================================*/
template
TypeTSP(Type**a,intn)
{
//定义traveling类型的变量Y
TravelingY;
//初始化Y
Y.n=n;
Y.x=newint[n+1];
Y.bestx=newint[n+1];
//置x为单位排列
for(inti=1;i<=n;i++)
{
Y.x[i]=i;
}
Y.a=a;
Y.cc=0;
Y.bestc=0;
Y.NoEdge=0;
//搜索x[2:
n]的全排列
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;
}
/*============================================================================
Swap函数实现两个数值交换。
需要注意的是形参那里一定要使用&符号,
不然的话修改之后的结果不会在其他函数中看到。
============================================================================*/
template
inlinevoidSwap(Type&a,Type&b)
{
Typetemp=a;
a=b;
b=temp;
}
/*============================================================================
main函数是主函数。
实现输入输出,调用之前的分支限界法函数ShortesPaths记录源到各顶点的最短路径长度。
并且同时在prev数组中记录其前驱结点。
根据图的prev数组,求出最短路径。
从终点开始,之后到终点的前驱结点,直至找到起点为止,就找出了到终点的路径。
将找到的到终点的路径,存储在trave_pre数组中。
因为trave_pre数组中存储的为倒序的路径,所以反向输出即为路径。
============================================================================*/
intmain()
{
cout<<"==========================================="<cout<<"==============回溯法求TSP问题=============="<cout<<"==========================================="<//输出图的顶点个数
cout<<"图的顶点个数为"<intbestlength;
//动态内存分配
int**a=newint*[N+1];
for(inti=0;i<=N;i++)
a[i]=newint[N+1];
//初始化
for(inti=0;i<=N;i++)
for(intj=0;ja[i][j]=0;
a[1][2]=30;a[1][3]=6;a[1][4]=4;
a[2][1]=30;a[2][3]=5;a[2][4]=10;
a[3][1]=6;a[3][2]=5;a[3][4]=20;
a[4][1]=4;a[4][2]=10;a[4][3]=20;
//开始计时
clock_tstart,end,over;
start=clock();
end=clock();
over=end-start;
start=clock();
//调用函数
bestlength=TSP(a,N);
//结束计时
end=clock();
//输出最短回路的长度
cout<<"最短回路的长为:
"<//输出时间
printf("Thetimeis%6.3f\n",(double)(end-start-over)/CLK_TCK);
//释放动态分配的内存
for(inti=0;i<=N;i++)
{
delete[]a[i];
}
delete[]a;
return0;
}