1.问题描述
TSP问题是指旅行家要旅行n个城市然后回到出发城市,要求各个城市经历且仅经历一次,并要求所走的路程最短。
2.算法思想
贪心法求解TSP问题有两种贪心策略。
1)最近邻点策略:从任意城市出发,每次在没有到过的城市中选择最近的一个,直到经过了所有的城市,最后回到出发城市。
给定初始的城市a,寻找与其邻接的最短距离的城市b,记录二者之间的路径并增加总路径长度;下一次从城市b开始,寻找与b邻接的最短距离的城市,循环查找,直到找到n-1条路径(n为城市个数),最后加上终点回到起点的边即可。
2)最短链接策略:每次在整个图的范围内选择最短边加入到解集合中,但是,要保证加入解集合中的边最终形成一个哈密顿回路。
首先按照城市之间距离远近进行排列,从距离最近的两个城市开始,如果这两个城市不在一个联通分量中并且度数均小于等于2,那么记录二者之间的路径,将它们划分到一个联通分量并将度数增加1;然后从距离第二小的两个城市开始,重复上述操作直到记录的路径有n-1条,最后找到度数为1的两个城市,作为最后一条路径。
3.参考代码
最近邻点策略:
/*TSP问题采用贪心法的最近邻点策略*/
/*
TSP问题是指旅行家需要旅行n个城市,要求各个城市经过且仅经过一次,
然后回到出发城市,并且所走的路径最短。
*/
/*
最近邻点策略:从任意城市出发,每次在没有到过的城市中选择最近的一个,
直到经过了所有的城市,最后回到出发城市
*/
#include<iostream>
#include<string>
#include<iomanip>
using namespace std;
//类TSP
class TSP
{
private:
int city_number; //城市个数
int **distance; //城市距离矩阵
int start; //出发点
int *flag; //标志数组,判断城市是否加入汉密顿回路
int TSPLength; //路径长度
public:
TSP(int city_num); //构造函数
void correct(); //纠正用户输入的城市距离矩阵
void printCity(); //输出用户输入的城市距离
void TSP1(); //贪心法的最近邻点策略求旅行商问题
};
//构造函数
TSP::TSP(int city_num)
{
int i=0,j=0;
int start_city;
city_number=city_num;
//初始化起点
cout<<"请输入本次运算的城市起点,范围为:"<<0<<"-"<<city_number-1<<endl;
cin>>start_city;
start=start_city;
//初始化城市距离矩阵
distance=new int*[city_number];
cout<<"请输入"<<city_number<<"个城市之间的距离"<<endl;
for(i=0;i<city_number;i++)
{
distance[i]=new int[city_number];
for(j=0;j<city_number;j++)
cin>>distance[i][j];
}
//初始化标志数组
flag=new int[city_number];
for(i=0;i<city_number;i++)
{
flag[i]=0;
}
TSPLength=0;
}
//纠正用户输入的城市代价矩阵
void TSP::correct()
{
int i;
for(i=0;i<city_number;i++)
{
distance[i][i]=0;
}
}
//打印城市距离
void TSP::printCity()
{
int i,j;
//打印代价矩阵
cout<<"您输入的城市距离如下"<<endl;
for(i=0;i<city_number;i++)
{
for(j=0;j<city_number;j++)
cout<<setw(3)<<distance[i][j];
cout<<endl;
}
}
//贪心法的最近邻点策略求旅行商问题
void TSP::TSP1()
{
int edgeCount=0;
int min,j;
int start_city=start; //起点城市
int next_city; //下一个城市
flag[start]=1;
cout<<"路径如下"<<endl;
while(edgeCount<city_number-1)//循环直到边数等于city_number-1
{
min=100;
for(j=0;j<city_number;j++)//求当前距离矩阵的最小值
{
if((flag[j]==0) && (distance[start_city][j] != 0) && (distance[start_city][j] < min))
{
next_city=j;
min=distance[start_city][j];
}
}
TSPLength+=distance[start_city][next_city];
flag[next_city] = 1;//将顶点加入汉密尔顿回路
edgeCount++;
cout<<start_city<<"-->"<<next_city<<endl;
start_city=next_city;//下一次从next_city出发
}
cout<<next_city<<"-->"<<start<<endl;//最后的回边
TSPLength+=distance[start_city][start];
cout<<"路径长度为"<<TSPLength;//哈密尔顿回路的长度
}
//主函数
int main()
{
cout<<"欢迎来到贪心法的最近邻点策略求旅行商问题,请输入城市个数";
int city_number;
while(cin>>city_number)
{
TSP tsp(city_number); //初始化
tsp.correct(); //纠正输入的城市距离矩阵
tsp.printCity(); //打印输入的城市
tsp.TSP1(); //求解
cout<<"-------------------------------------------------------"<<endl;
cout<<"\n欢迎来到贪心法的最近邻点策略求旅行商问题,请输入城市个数";
}
return 0;
}
最短链接策略:
/*TSP问题采用贪心法的最短链接策略*/
/*
TSP问题是指旅行家需要旅行n个城市,要求各个城市经过且仅经过一次,
然后回到出发城市,并且所走的路径最短。
*/
/*
最短链接策略:每次在整个图的范围内选择最短的边加入到解集合中,保证解集合中的边最终形成一个哈密尔顿回路
*/
#include<iostream>
#include<string>
#include<iomanip>
#include<algorithm>
using namespace std;
/*TSP类*/
class TSP
{
public:
int start_city; //起点城市
int end_city; //终点城市
int distance; //两者之间的距离
TSP(int sta,int end,int dis); //构造函数
TSP(); //默认构造函数
void init(int sta,int end,int dis); //初始化函数
void printfInfo(); //打印信息
};
/*函数原型声明*/
int cmp(TSP t1,TSP t2); //定义排序规则
int TSP2(TSP *t,int city_num,int cities,TSP *process); //最短链接求TSP
int find(int x,int *pre); //寻找前置结点
void join(int x,int y,int *pre); //合并结点
//构造函数
TSP::TSP(int sta,int end,int dis)
{
start_city=sta;
end_city=end;
distance=dis;
}
//默认构造函数
TSP::TSP()
{
}
//初始化函数
void TSP::init(int sta,int end,int dis)
{
start_city=sta;
end_city=end;
distance=dis;
}
//打印信息函数
void TSP::printfInfo()
{
cout<<start_city<<"\t"<<end_city<<"\t"<<distance<<"\t\n";
}
//排序规则
int cmp(TSP t1,TSP t2)
{
return t1.distance<t2.distance;
}
//最短链路求解TSP问题
int TSP2(TSP *t,int city_num,int cities,TSP *process)
{
int TSPLength=0;
int i=0,j=0,k=0;
int *flag=new int[city_num]; //记录结点的度数
int *pre=new int[city_num]; //记录结点的前置结点
for(i=0;i<city_num;i++)
{
flag[i]=0; //初始结点度数为0
pre[i]=i; //每个结点的前置结点设置为自己
}
do{
for(i=0;i<cities;i++)
{
if(find(t[i].start_city,pre)!=find(t[i].end_city,pre))//如果边连接的点属于不同的集合
{
if(flag[t[i].start_city]<=2 && flag[t[i].end_city]<=2)//不会产生分支
{
flag[t[i].start_city]++; //度数加1
flag[t[i].end_city]++;
TSPLength+=t[i].distance; //增加路径长度
//保存路径
process[k].start_city=t[i].start_city;
process[k].end_city=t[i].end_city;
process[k].distance=t[i].distance;
// cout<<"从城市"<<t[i].start_city<<"到城市"<<t[i].end_city<<",距离为"<<t[i].distance<<endl;
j++;
k++;
join(t[i].start_city,t[i].end_city,pre);//两点所在的集合合并
}
}
}
}while(j<city_num-1);
//找到度数为1的两个点
int m,n;
for(m=0;m<city_num;m++)
if(flag[m]==1)
{
process[k].start_city=m;
flag[m]++;
break;
}
for(n=0;n<city_num;n++)
if(flag[n]==1)
{
process[k].end_city=n;
flag[n]++;
break;
}
//找到最后一条边的长度
for(i=0;i<cities;i++)
if(t[i].start_city==m && t[i].end_city==n)
process[k].distance=t[i].distance;
TSPLength+=process[k].distance;
return TSPLength;
}
//找前置结点
int find(int x,int *pre)
{
int r=x;
while(pre[r]!=r)
r=pre[r]; //找到前导结点
int i=x,j;
while(i!=r) //路径压缩算法
{
j=pre[i]; //记录x的前导结点
pre[i]=r; //将i的前导结点设置为r根节点
i=j;
}
return r;
}
//合并结点
void join(int x,int y,int *pre)
{
int a=find(x,pre); //x的根节点为a
int b=find(y,pre); //y的根节点为b
if(a!=b) //如果a,b不是相同的根节点,则说明ab不是连通的
{
pre[a]=b; //将ab相连 将a的前导结点设置为b
}
}
//主函数
int main()
{
cout<<"欢迎来到贪心法的最短链路策略求旅行商问题,请输入城市个数";
int city_number,cities=0;
while(cin>>city_number)
{
int i,j,k=0;
for(i=1;i<city_number;i++)
cities+=i;
//声明
TSP *tsp=new TSP[cities]; //记录所有城市的路径
TSP *process=new TSP[city_number]; //最短链接求解的路径
//初始化代价矩阵
cout<<"请输入城市的代价矩阵"<<endl;
int **distance;
distance=new int*[city_number];
for(i=0;i<city_number;i++)
{
distance[i]=new int[city_number];
for(j=0;j<city_number;j++)
{
cin>>distance[i][j];
}
}
//生成对象数组
for(i=0;i<city_number;i++)
{
for(j=i+1;j<city_number;j++)
{
tsp[k].init(i,j,distance[i][j]);
k++;
}
}
//输出对象数组
cout<<"起点\t"<<"终点\t"<<"距离\t"<<endl;
for(i=0;i<cities;i++)
{
tsp[i].printfInfo();
}
cout<<"-------------------------------------"<<endl;
//排序
sort(tsp,tsp+cities,cmp);
cout<<"\n排序后"<<endl;
cout<<"起点\t"<<"终点\t"<<"距离\t"<<endl;
for(i=0;i<cities;i++)
{
tsp[i].printfInfo();
}
cout<<"-------------------------------------"<<endl;
//最短链接求解TSP问题
cout<<"最短链接求解过程:"<<endl;
int length=TSP2(tsp,city_number,cities,process);
cout<<"起点\t"<<"终点\t"<<"距离\t"<<endl;
for(i=0;i<city_number;i++)
{
process[i].printfInfo();
}
cout<<"路径长度:"<<length<<endl;
delete[] tsp;
}
return 0;
}
4.参考结果
最近邻点:
最短链路:
5.补充
并查集:通过前置结点来判断当前的结点是否属于一个联通分量,不属于则合并两个结点,合并的方法就是将其中一个结点的前置结点设置为另一个结点;当然还可以压缩路径,计算过程中记录结点的根结点,这样就不用依次向上遍历了。
关于sort(),sort并不是简单的快速排序,它对普通的快速排序进行了优化,此外,它还结合了插入排序和推排序。系统会根据数据形式和数据量自动选择合适的排序方法,这并不是说每次排序只选择一种方法,是在一次完整排序中不同的情况选用不同方法,比如给一个数据量较大的数组排序,开始采用快速排序,分段递归,分段之后每一段的数据量达到一个较小值后它就不继续往下递归,而是选择插入排序,如果递归的太深,会选择推排序。