TSP问题的解决与实现Word格式文档下载.docx
《TSP问题的解决与实现Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《TSP问题的解决与实现Word格式文档下载.docx(28页珍藏版)》请在冰豆网上搜索。
最短路径为v0v1v4v2v3
2.需求分析
(1)本程序用于求解n个结点的最短哈密尔顿回路问题。
(2)程序运行后显示提示信息—“Pleaseinsertthenumberofcities:
”,例如用户输入5,则表示结点n的值为5;
接下来程序输出提示信息—“Pleaseinsertthedistancebetweenonecityandanother:
”,例如用户输入测试数据中给出的路程矩阵,表示任意两个城市之间的距离,比如第一个城市到第0个城市之间的距离为25。
(3)用户输入数据完毕,程序将输出运算结果。
(4)测试数据均为正数,其中用999来表示两个城市之间距离为∞。
3.概要设计
为了实现上述程序功能,使用优先队列来维护结点表,因此需要图和队列两个抽象数据类型。
(1)图的抽象数据类型定义
ADTGraph{
Data:
具有相同类型的数据元素的集合,称为顶点集。
Relation:
顶点偶对的有穷集合。
Operation:
CreateGraph(&
G,V,VR)
初始条件:
V是图中顶点集合,VR是图中顶点偶对集合。
操作条件:
按照V和VR的定义构造图G。
DestroyGraph(&
G)
图G已经存在。
操作结果:
销毁G。
LocateVex(G,u)
图G已经存在,u和G中顶点有相同类型。
操作结果:
如果G中存在u,则返回u在G中的位置;
否则返回相应信息。
GetVex(G,v)
图G已经存在,v是G中某个顶点。
返回v的值。
PutVex(&
G,v,value)
图G已经存在,v是G中顶点。
对v赋值value。
FirstAdjvex(G,v)
返回v的第一个邻接顶点。
如果v在G中没有邻接顶点,则返回相应信息。
NextAdjvex(G,v,w)
图G已经存在,v是G中顶点,w是v的邻接顶点。
返回v的下一个邻接顶点。
如果w是v的最后一个邻接点,则返回相应信息。
InsertVex(&
G,v,w)
图G已经存在,v和w是G中顶点。
在G中添加v。
DeleteVex(&
G,v)
删除G中顶点v及其相关的边。
InsertEdge(&
在G中添加<
v,w>
;
如果G是无向图,则还增添<
w,v>
。
DeleteEdge(&
在G中删除<
;
如果G是无向图,则还删除<
DFSTraverse(G,v)
从v起深度访问G。
BFSTraverse(G,v)
从v起广度访问G。
}ADTGraph
(2)队列的抽象数据类型
ADTQueue{
Data:
具有相同数据类型的及先进先出特性的数据元素集合。
Relation:
相邻数据元素具有前驱和后继的关系。
InitQueue(&
Q)
无
创造一个空队列Q。
DestroyQueue(&
队列Q已经存在。
销毁Q。
ClearQueue(&
重置Q为空队列。
QueueLength(Q)
返回Q的元素个数。
GetHead(Q,&
e)
队列Q已经存在并且非空。
用e返回Q的队头元素。
EnQueue(&
Q,e)
DeQueue(&
Q,&
队列Q已经存在且非空。
删除Q的队头元素,并用e返回其值。
}ADTQueue
(3)本程序包含三大模块:
主程序模块,TSP算法模块,辅函数模块。
三大模块之间的调用关系如下。
4.详细设计
(1)元素类型、结点类型和指针类型、变量和数据结构声明
Node*xnode;
//父亲结点指针
Node*ynode;
//儿子结点指针
Node*znode;
Node*qbase;
//优先队列首指针
ElemTypebound;
//当前可行解的最优值
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;
Q.rear->
next=p;
Q.rear=p;
}//EnQueue
Node*DeQueue(LinkQueue&
//若链队列Q为空,则返回NULL;
否则返回指向数据的指针
QNode*p;
Node*e;
if(Q.front->
next==NULL)returnNULL;
p=Q.front->
next;
e=p->
data;
next=p->
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]<
node->
c[row][1]){
temp=node->
c[row][0];
second=node->
c[row][1];
}
else{
for(i=2;
i<
k;
i++){
c[row][i]<
temp){
second=temp;
c[row][i];
elseif(node->
second)
returntemp;
ElemTypeCol_min(Node*node,intcol,ElemType&
//计算路程矩阵列的最小值
c[0][col]<
c[1][col]){
c[0][col];
c[1][col];
c[i][col]<
c[i][col];
ElemTypeArray_red(Node*node){
//归约node所指向的结点的路程矩阵
sum=0;
for(i=0;
i++){//行归约
temp=Row_min(node,i,temp1);
//行归约常数
for(j=0;
j<
j++)
node->
c[i][j]-=temp;
sum+=temp;
//行归约常数累计
for(j=0;
j++){//列归约
temp=Col_min(node,j,temp1);
//列归约常数
i++)
returnsum;
ElemTypeEdge_sel(Node*node,int&
vk,intv1){
//计算Dkl,选择搜索分支的边
d=0;
ElemType*row_value=newElemType[node->
k];
ElemType*col_value=newElemType[node->
i++)//每一行的次小值
Row_min(node,i,row_value[i]);
for(i=0;
i++)//每一列的次小值
Col_min(node,i,col_value[i]);
i++){//对路程矩阵所有的0元素值
j++){//计算相应的temp值
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;
k-1;
i++)//元素上移
v1;
c[i][j]=node->
c[i+1][j];
for(j=v1;
j++)//元素左移
vk;
c[i][j+1];
for(i=vk;
i++)//元素上移及左移
for(j=v1;
j++)
c[i+1][j+1];
vk1=node->
init_row[vk];
//当前行转换为原始行vk1
row_cur[vk1]=-1;
//原始行vk1置删除标志
for(i=vk1+1;
n;
i++)//vk1之后的原始行,其对应的当前行号减1
row_cur[i]--;
vl1=node->
init_col[v1];
//当前列v1转换为原始列vl1
col_cur[vl1]=-1;
//原始列vl1置删除标志
for(i=vl1+1;
i++)//vl1之后的原始列,其对应的当前列号减1
col_cur--;
i++)//修改vk及其后的当前行的对应原始行号
init_row[i]=node->
init_row[i+1];
for(i=v1;
i++)//修改v1及其后的当前列的对应原始行号
node->
init_col[i]=node->
init_col[i+1];
k--;
//当前矩阵的阶数减1
voidEdge_byp(Node*node,intvk,intvl){
//登记回路顶点邻接表,旁路有关的边
vk=init_row[vk];
//当前行号转换为原始行号
vl=init_col[vl];
//当前列号转换为原始列号
ad[vk]=vl;
//登记回路顶点邻接表
k=node->
row_cur[vl];
//vl转换为当前行号k
l=node->
col_cur[vk];
//vk转换为当前列号l
if((k>
=0)&
&
(l>
=0))//当前行、列号均处于当前矩阵中
c[k][l]=MAX_VALUE_OF_TYPE;
//旁路相应的边
Node*Initial(ElemTypec[][],intn){
//初始化
Node*node=newNode;
//分配结点缓冲区
i++)//复制路程矩阵的初始数据
c[i][j]=c[i][j];
i++){//建立路程矩阵原始行、列号与初始行、列号对应的关系
init_row[i]=i;
init_col[i]=i;
row_cur=i;
col_cur=i;
i++)//回路顶点邻接表初始化为空
ad[i]=-1;
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结点(右儿子结点)
*znode=*xnode;
//x结点数据复制给z结点
znode->
c[vk][vl]=MAX_VALUE_OF_TYPE;
//旁路结点的边
Array_red(znode);
//归约z结点路程矩阵
w=xnode->
w+d;
//计算z结点的下界
if(znode->
w<
bound)//若下界小于当前可行解最优值
EnQueue(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->
//归约y结点路程矩阵
w+=xnode->
w;
//计算y结点的下界
if(ynode->
k==2){//路程矩阵只剩2阶
if((ynode->
c[0][0]==0)&
(ynode->
c[1][1]==0)){
ad[ynode->
init_row[0]]=ynode->
init_col[0];
init_row[1]]=ynode->
init_col[1];
}//登记最后的两条边
ynode->
k=0;
bound){//若下界小于当前可行解最优值
EnQueue(qbase,ynode);
//y结点插入优先队列
if(ynode->
k==0)//更新当前可行解最优值
bound=ynode->
elsedeleteynode;
//否则剪去y结点
xnode=DeQueue(qbase);
//取优先队列首元素
//保存最短路线长度
i++)//保存路线的顶点邻接表
ad[i]=xnode->
ad[i];
deletexnode;
//释放x结点缓冲区
while(qbase.front!
=qbase.rear){//释放队列节点缓冲区
xnode=DeQueue(qbase);
deletexnode;
cout<
<
"
Thewayis:
for(intm=0;
m<
m++)
ad[m];
endl;
cout<
Thelengthofthewayis:
(5)主函数的伪码算法
voidmain(){
”Pleaseinsertthenumberofcities:
”;
cin>
>
”Pleaseinsertthedistancebetweenonecityandanother:
{cin>
c[i][j];
””;
TSP(c,n,ad);
}//main
(6)函数调用关系图
5.调试分析
(1)
使用分支限界法求解的过程中,将动态地生成很多结点,用结点表来存放动态生成的结点信息。
因此必须按路程的下界来确定搜索的方向,因此可以用优先队列或堆来维护结点表。
在此使用优先队列来维护结点表。
(2)算法的时间复杂度分析
TSP算法的时间估计如下:
a.初始化父亲结点,归约父亲结点路程矩阵,都需要O(n2)时间。
b.while循环体的执行次数取决于所搜索的结点个数,假定所搜索的借点数为c。
在while循环内部,选择分支方向需要O(n2)时间。
c.把x结点数据复制到z结点(这里包括整个路程矩阵的复制工作),归约z结点的路程矩阵,都需要O(n2)时间。
d.把z结点插入到优先队列,在最坏的情况下需要O(c)时间。
e.把x结点复制到y结点,同样需要O(n2)时间。
f.登记回路邻接表,旁路有关的边,只需O
(1)时间。
g.删除y结点路程矩阵当前vk行vl列,归约y结点的路程矩阵,这些操作都需要O(n2)时间。
h.把y结点插入队列,删除队列首元素,都需要O(c)时间。
i.其余的花费为O
(1)时间。
j.因此,整个while循环在最坏的情况下需要O(cn2)时间。
k.最后,在算法的尾部,for循环保存路线的顶点邻接表于数组ad中作为算法的返回值,需要O(n)时间。
l.释放队列缓冲区的while循环在最坏情况下需要O(c)时间。
所以,整个算法的时间复杂度为O(cn2)。
(3)算法的空间复杂度分析
算法所需要的空间,主要花费在结点的存储空间。
每个结点需要O(n2)空间存放