TSP问题的解决与实现报告.docx
《TSP问题的解决与实现报告.docx》由会员分享,可在线阅读,更多相关《TSP问题的解决与实现报告.docx(34页珍藏版)》请在冰豆网上搜索。
![TSP问题的解决与实现报告.docx](https://file1.bdocx.com/fileroot1/2023-2/3/7bc19e03-2438-40a3-bc02-c40720e546b7/7bc19e03-2438-40a3-bc02-c40720e546b71.gif)
TSP问题的解决与实现报告
1.问题描述
所谓TSP问题是指旅行家要旅行n个城市,要求各个城市经历且仅经历一次,并且要求所走的路程最短。
该问题又称为货郎担问题、邮递员问题、售货员问题,是图问题中最广为人知的问题。
2.基本要求
(1)上网查找TSP问题的应用实例;
(2)分析求TSP问题的全局最优解的时间复杂度;
(3)设计一个求近似解的算法;
(4)分析算法的时间复杂度。
3.提交报告
课程设计报告提交内容包括:
(1)问题描述;
(2)需求分析;(3)概要设计;(4)详细设计;(5)调试分析;(6)使用说明;(7)测试结果;(8)附录(带注释的源程序)。
参见“数据结构课程设计概述.pdf”和“数据结构课程设计示例.pdf”。
指导教师(签字):
系主任(签字):
批准日期:
2014年月日
1.问题描述
(1)题目要求
旅行家要旅行n个城市,要求各个城市经历且仅经历一次,最终要回到出发的城市,求出最短路径。
用图论的术语来说,假如有一个图G=(V,E),其中V是顶点集,E是边集,设D=(d)是由ij顶点i和顶点j之间的距离所组成的距离矩阵。
TSP问题就是求出一条通过每个顶点且每个顶点只通过一次的具有最短距离的回路。
.
)基本要求(2上网查找TSP问题的应用实例;a.
分析求TSP问题的全局最优解的时间复杂度;b.
c.设计一个求近似解的算法;d.分析算法的时间复杂度。
3)测试数据(TSP问题:
5个城市的注:
由于矩阵所表示的是两个城市之间的距离,所以该矩阵为对称矩阵
路程矩阵如图所示:
02314
282532∞410
25∞1831261
4118∞71
2
∞1132317
3
2826111∞4
最短路径为vvvvv341202.需求分析
(1)本程序用于求解n个结点的最短哈密尔顿回路问题。
(2)程序运行后显示提示信息—“”,例如用户输入5,则表示结Pleaseinsertthenumberofcities:
点n的值为5;接下来程序输出提示信息—“Pleaseinsertthedistancebetweenonecityand
”,例如用户输入测试数据中给出的路程矩阵,表示任意两个城市之间的距离,比another:
如第一个城市到第0个城市之间的距离为25。
(3)
用户输入数据完毕,程序将输出运算结果。
(4)测试数据均为正数,其中用999来表示两个城市之间距离为∞。
3.概要设计
为了实现上述程序功能,使用优先队列来维护结点表,因此需要图和队列两个抽象数据类型。
.
(1)图的抽象数据类型定义
ADTGraph{
Data:
具有相同类型的数据元素的集合,称为顶点集。
:
Relation顶点偶对的有穷集合。
:
Operation
CreateGraph(&G,V,VR)
初始条件:
是图中顶点集合,是图中顶点偶对集合。
VRV
操作条件:
按照和的定义构造图。
VGVR
DestroyGraph(&G)
初始条件:
图已经存在。
G
操作结果:
销毁。
G
LocateVex(G,u)
初始条件:
图已经存在,和中顶点有相同类型。
uGG
操作结果:
如果中存在则返回在中的位置;否则返回相应信息。
Guu,G
GetVex(G,v)
初始条件:
图已经存在,是中某个顶点。
vGG
操作结果:
返回的值。
v
PutVex(&G,v,value)
初始条件:
图已经存在,是中顶点。
GGv
操作结果:
对赋值。
vvalue
FirstAdjvex(G,v)
初始条件:
图已经存在,是中顶点。
vGG
操作结果:
返回的第一个邻接顶点。
如果在中没有邻接顶点,则返回相应信息。
vvG
NextAdjvex(G,v,w)
初始条件:
图已经存在,是中顶点是的邻接顶点。
GvG,wv
操作结果:
返回的下一个邻接顶点。
如果是的最后一个邻接点,则返回相应信息。
vwv
InsertVex(&G,v,w)
初始条件:
图已经存在,和是中顶点。
GwvG
操作结果:
在中添加。
vG
DeleteVex(&G,v)
初始条件:
图已经存在,是中顶点。
GGv
操作结果:
删除中顶点及其相关的边。
Gv,v,w)
InsertEdge(&G.
初始条件:
图已经存在,和是中顶点。
wGGv
操作结果:
在中添加;如果是无向图,则还增添。
GG
DeleteEdge(&G,v,w)
初始条件:
图已经存在,和是中顶点。
GwvG
操作结果:
在中删除如果是无向图,则还删除。
GG;
DFSTraverse(G,v)
初始条件:
图已经存在,是中顶点。
GGv
操作结果:
从起深度访问。
Gv
BFSTraverse(G,v)
初始条件:
图已经存在,是中顶点。
GGv
操作结果:
从起广度访问。
vG
}ADTGraph
(2)队列的抽象数据类型
ADTQueue{
Data:
具有相同数据类型的及先进先出特性的数据元素集合。
:
Relation
相邻数据元素具有前驱和后继的关系。
:
Operation
InitQueue(&Q)
初始条件:
无
操作结果:
创造一个空队列。
Q
DestroyQueue(&Q)
初始条件:
队列已经存在。
Q
操作结果:
销毁。
Q
ClearQueue(&Q)
初始条件:
队列已经存在。
Q
操作结果:
重置为空队列。
Q
QueueLength(Q)
初始条件:
队列已经存在。
Q
操作结果:
返回的元素个数。
Q
,&e)
GetHead(Q初始条件:
队列已经存在并且非空。
Q
操作结果:
用返回的队头元素。
eQEnQueue(&Q,e)
初始条件:
队列已经存在。
Q
操作结果:
重置为空队列。
Q
,&e)
DeQueue(&Q初始条件:
队列已经存在且非空。
Q
操作结果:
删除的队头元素,并用返回其值。
eQ
}ADTQueue
(3)本程序包含三大模块:
主程序模块,TSP算法模块,辅函数模块。
三大模块之间的调用关系如下。
主函数模块
算法模块TSP
辅函数模块
.详细设计4元素类型、结点类型和指针类型、变量和数据结构声明
(1)
父亲结点指针//Node*xnode;
//儿子结点指针Node*ynode;
儿子结点指针//Node*znode;
优先队列首指针//Node*qbase;
当前可行解的最优值//;boundElemType
typedefintElemType;//元素类型
#defineMAX_VALUE_OF_TYPE999;//代表∞
城市顶点用数字0,1,2,……,n-1编号。
在搜索的过程中,各个结点的数据是动态变化的,互不相同,发生回溯时,必须使用结点中原来的数据。
因此,每个结点的数据必须是局部与该结点的。
用如下的数据结构来定义结点中所使用的数据:
typedefstructNode{
ElemTypec[100][100];//路程矩阵
intinit_row[100];//路程矩阵的当前行映射为原始行
intinit_col[100];//路程矩阵的当前列映射为原始列
intcur_row[100];//路程矩阵的原始行映射为当前行
intcur_col[100];//路程矩阵的原始列映射为当前列
intad[100];//回路顶点邻接表
intk;//当前路程矩阵的阶
ElemTypew;//结点的下界
structNode*next;//队列链指针
}Node;
(2)队列类型
typedefstructQNode{
Node*data;//数据域
structQNode*next;//指针域
}QNode,*QueuePtr;
typedefstruct{
QueuePtrfront;//头指针,指向链队头结点
QueuePtrrear;//尾指针,指向链队列最后一个结点
}LinkQueue;
程序中所用到的关于优先队列基本操作实现的伪码算法如下:
voidInitQueue(LinkQueue&Q){
//构造一个空链队列Q
Q.front=Q.rear=newQNode;
Q.front->next=NULL;
}//InitQueue
voidEnQueue(LinkQueue&Q,Node*e){
//插入一个指针e到链队列Q中,成为新的队尾指针
QueuePtrp;
p=newQNode;
p->data=e;
p->next=NULL;
Q.rear->next=p;
Q.rear=p;
}//EnQueue
Node*DeQueue(LinkQueue&Q){
//若链队列Q为空,则返回NULL;否则返回指向数据的指针
QNode*p;
Node*e;
if(Q.front->next==NULL)returnNULL;
p=Q.front->next;
e=p->data;
Q.front->next=p->next;
if(Q.rear==p)Q.rear=Q.front;
deletep;
returne;
}//DeQueue
(3)辅助函数的实现(共7个)
ElemTypeRow_min(Node*node,introw,ElemType&second){
//计算路程矩阵行的最小值
if(node->c[row][0]c[row][1]){
temp=node->c[row][0];
second=node->c[row][1];
}
else{
temp=node->c[row][1];
second=node->c[row][0];
}
for(i=2;ik;i++){
if(node->c[row][i]second=temp;
temp=node->c[row][i];
}
elseif(node->c[row][i]second=node->c[row][i];
}
returntemp;
}
ElemTypeCol_min(Node*node,intcol,ElemType&second){
//计算路程矩阵列的最小值
if(node->c[0][col]c[1][col]){
temp=node->c[0][col];
second=node->c[1][col];
}
else{
temp=node->c[1][col];
second=node->c[0][col];
}
for(i=2;ik;i++){
if(node->c[i][col]second=temp;
temp=node->c[i][col];
}
elseif(node->c[i][col]second=node->c[i][col];
}
returntemp;
}
ElemTypeArray_red(Node*node){
//归约node所指向的结点的路程矩阵
sum=0;
for(i=0;ik;i++){//行归约
temp=Row_min(node,i,temp1);//行归约常数
for(j=0;jk;j++)
node->c[i][j]-=temp;
sum+=temp;//行归约常数累计
}
for(j=0;jk;j++){//列归约
temp=Col_min(node,j,temp1);//列归约常数
for(i=0;ik;i++)
node->c[i][j]-=temp;
sum+=temp;
}
returnsum;
}
ElemTypeEdge_sel(Node*node,int&vk,intv1){
//计算D,选择搜索分支的边kld=0;
ElemType*row_value=newElemType[node->k];
ElemType*col_value=newElemType[node->k];
for(i=0;ik;i++)//每一行的次小值
Row_min(node,i,row_value[i]);
for(i=0;ik;i++)//每一列的次小值
Col_min(node,i,col_value[i]);
for(i=0;ik;i++){//对路程矩阵所有的0元素值
for(j=0;jk;j++){//计算相应的temp值
if(node->c[i][j]==0){
temp=row_value[i]+col_value[i];
if(temp>d){//求最大的temp值于d
d=temp;
vk=i;
v1=j;
}//保存相应的行、列号
}
}
}
deleterow_value;
deletecol_value;
returnd;
}
voidDel_rowcol(Node*node,intvk,intv1){
//删除路程矩阵的第vk行和第v1列的所有元素
for(i=vk;ik-1;i++)//元素上移
for(j=0;jnode->c[i][j]=node->c[i+1][j];
for(j=v1;jk-1;j++)//元素左移
for(i=0;inode->c[i][j]=node->c[i][j+1];
//元素上移及左移for(i=vk;ik-1;i++)
for(j=v1;jk-1;j++)
node->c[i][j]=node->c[i+1][j+1];
vk1//当前行转换为原始行vk1=node->init_row[vk];
//原始行vk1置删除标志node->row_cur[vk1]=-1;
之后的原始行,其对应的当前行号减1//vk1for(i=vk1+1;inode->row_cur[i]--;
//当前列v1转换为原始列vl1vl1=node->init_col[v1];
置删除标志//原始列vl1node->col_cur[vl1]=-1;
1//vl1之后的原始列,其对应的当前列号减for(i=vl1+1;inode->col_cur--;
for(i=vk;ik;i++)修改//vk及其后的当前行的对应原始行号
node->init_row[i]=node->init_row[i+1];
修改//v1及其后的当前列的对应原始行号for(i=v1;ik;i++)
node->init_col[i]=node->init_col[i+1];
1//当前矩阵的阶数减node->k--;
}
voidEdge_byp(Node*node,intvk,intvl){
//登记回路顶点邻接表,旁路有关的边//当前行号转换为原始行号vk=init_row[vk];
vl=init_col[vl];当前列号转换为原始列号//
登记回路顶点邻接表//node->ad[vk]=vl;
k=node->row_cur[vl];//vl转换为当前行号k
l=node->col_cur[vk];//vk转换为当前列号l
if((k>=0)&&(l>=0))//当前行、列号均处于当前矩阵中
node->c[k][l]=MAX_VALUE_OF_TYPE;//旁路相应的边
}
Node*Initial(ElemTypec[][],intn){
//初始化
Node*node=newNode;//分配结点缓冲区
for(i=0;ifor(j=0;jnode->c[i][j]=c[i][j];
for(i=0;inode->init_row[i]=i;
node->init_col[i]=i;
node->row_cur=i;
node->col_cur=i;
}
for(i=0;inode->ad[i]=-1;
node->k=n;
returnnode;//返回结点指针
}
(4)TSP问题分支限界算法
voidTSP(ElemTypec[100][100],intn,int*ad){
bound=MAX_VALUE_OF_TYPE;
InitQueue(qbase);//初始化队列
xnode=Initial(c,n);//初始化父亲结点--------x结点
xnode->w=Array_red(xnode);//归约路程矩阵
while(xnode->k!
=0){
d=Edge_sel(xnode,vk,vl);//选择分支方向并计算Dkl
znode=newNode;//建立分支结点------z结点(右儿子结点)
结点z结点数据复制给//x*znode=*xnode;
znode->c[vk][vl]=MAX_VALUE_OF_TYPE;//旁路结点的边
Array_red(znode);//归约z结点路程矩阵
znode->w=xnode->w+d;//计算z结点的下界
if(znode->wEnQueue(qbase,znode);//将z结点插入优先队列
elsedeleteznode;//否则,剪去该结点
ynode=newNode;//建立分支节点----y结点(左儿子结点)
*ynode=*xnode;//x结点数据复制到y结点
Edge_byp(ynode,vk,vl);//登记回路邻接表,旁路有关的边
Del_rowcol(ynode,vk,vl);//删除结点y路程矩阵当前vk行vl列
ynode->w=Array_red(xnode);//归约y结点路程矩阵
ynode->w+=xnode->w;//计算y结点的下界
if(ynode->k==2){//路程矩阵只剩2阶
if((ynode->c[0][0]==0)&&(ynode->c[1][1]==0)){
ynode->ad[ynode->init_row[0]]=ynode->init_col[0];
ynode->ad[ynode->init_row[1]]=ynode->init_col[1];
}
else{
ynode->ad[ynode->init_row[0]]=ynode->init_col[1];
ynode->ad[ynode->init_row[1]]=ynode->init_col[0];
}//登记最后的两条边
ynode->k=0;
}
if(ynode->wEnQueue(qbase,ynode);//y结点插入优先队列
if(ynode->k==0)//更新当前可行解最优值
bound=ynode->w;
}
elsedeleteynode;//否则剪去y结点
xnode=DeQueue(qbase);//取优先队列首元素
}
w=xnode->w;//保存最短路线长度
for(i=0;iad[i]=xnode->ad[i];
deletexnode;//释放x结点缓冲区
释放队列节点缓冲区//while(qbase.front!
=qbase.rear){
xnode=DeQueue(qbase);
deletexnode;
}
cout<;
for(intm=0;mcout<cout<cout<<}
(5)主函数的伪码算法
voidmain(){
cout<<”Pleaseinsertthenumberofcities:
”;
cin>>n;
cout<<”Pleaseinsertthedistancebetweenonecityandanother:
”;
for(i=0;ifor(j=0;j{cin>>c[i][j];
cout<<””;}
cout<}
TSP(c,n,ad);}//main
(6)函数调用关系图
main
TSP
EnQueue
Initial
Array_red
Edge_sel
Edge_byp
DeQueueInitQueue
Del_rowcol
Row_min
5.调试分析
(1)
使用分支限界法求解的过程中,将动态地生成很多结点,用结点表来存放动态生成的结点信息。
因此必须按路