回溯法求旅行售货员问题物联1301班刘悦08080112.docx

上传人:b****8 文档编号:10106654 上传时间:2023-02-08 格式:DOCX 页数:18 大小:120.95KB
下载 相关 举报
回溯法求旅行售货员问题物联1301班刘悦08080112.docx_第1页
第1页 / 共18页
回溯法求旅行售货员问题物联1301班刘悦08080112.docx_第2页
第2页 / 共18页
回溯法求旅行售货员问题物联1301班刘悦08080112.docx_第3页
第3页 / 共18页
回溯法求旅行售货员问题物联1301班刘悦08080112.docx_第4页
第4页 / 共18页
回溯法求旅行售货员问题物联1301班刘悦08080112.docx_第5页
第5页 / 共18页
点击查看更多>>
下载资源
资源描述

回溯法求旅行售货员问题物联1301班刘悦08080112.docx

《回溯法求旅行售货员问题物联1301班刘悦08080112.docx》由会员分享,可在线阅读,更多相关《回溯法求旅行售货员问题物联1301班刘悦08080112.docx(18页珍藏版)》请在冰豆网上搜索。

回溯法求旅行售货员问题物联1301班刘悦08080112.docx

回溯法求旅行售货员问题物联1301班刘悦08080112

算法分析与设计实验报告

第X次实验

姓名

刘悦

学号

201308080112

班级

物联1301班

时间

12.26上午

地点

工训楼C栋309

实验名称

回溯法求旅行售货员问题

实验目的

通过上机实验,掌握回溯算法的思想,利用回溯法求旅行售货员问题并实现。

实验原理

旅行售货员问题的解空间树是一棵排列树。

当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]的费用。

实验步骤

1从起点开始,找其子结点,即存在一条边相连。

2如果费用小于当前最优值则以此结点继续向下探索,否则就剪去相应的子树。

3当扩展到叶结点的父结点时,检测此结点与叶结点,叶结点与起点是否有边相连。

4若均有边相连,则检测此回路的费用是否小于最优费用。

5若小于最优费用,则更新最优费用,同时记录最优解。

关键代码

//定义图的顶点数

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;

}

 

测试结果

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;j

a[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;

}

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 党团工作 > 入党转正申请

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1