一、
TSP(旅行商问题)简介
TSP,即旅行商问题,又称TSP问题(Traveling Salesman
Problem),是数学领域中著名问题之一。
假设有一个旅行商人要拜访N个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求得的路径路程为所有路径之中的最小值
最近刚好在学习路径规划这一类的问题,简单了解了一下TSP问题,发现csdn上C++版本的并不是很多,主要尝试了两种代码
二、代码实现(C++)
第一种:
#include<iostream>
#define n 7
using namespace std;
int pd = 0;
int s[n] = {-1,-1,-1,-1,-1,-1,-1} ; // 记录已经访问过的城市, 初始化为-1,表示未访问
int distance[n][n] = {{10000,48,38,48,40,50,34}, // 城市间距离数组,10000表示自己到自己的距离
{48,10000,22,26,56,46,50},
{38,22,10000,28,46,40,40},
{48,26,28,10000,44,34,50},
{40,56,46,44,10000,22,26},
{50,46,40,34,22,10000,28},
{34,50,40,50,26,28,10000}}; //设定邻接矩阵
bool visit(int k) //判断城市k是否被访问过
{
for (int i = 0; i < n ; i++)
if (s[i] == k) return 1 ;
return 0;
}
void clostCityDistance(int currentCity) //查找距离当前城市最近的下一个城市
{
int Dtemp = 10000; //Dtemp暂时存储当前最小路径的值
pd++;
for (int i = 0; i < n; i++)
{
if ( visit(i) == 0 && ::distance[i][s[currentCity]] <Dtemp )
{
Dtemp = ::distance[i][s[currentCity]] ;
s[pd] = i ; //若该城市没有被访问过,且距离当前城市最短,则将访问该城市,存入s[]数组中
}
}
for(int i = 0 ; i < 7 ; i++)
cout<<s[i];
cout<<endl;
for (int i = 0; i < n; i++) //查找是否还有未访问的城市
{
if ( s[i] == -1 )
clostCityDistance(s[pd]);
}
}
void TSP()
{
int sum = 0 ;// 最短路径之和
s[0] = 0;//从第2个城市出发 ,初始化出发的城市,可在0,1,2,3中任意一个
clostCityDistance(s[0]) ;//寻找距离2城市最近的城市
for (int i=0; i < n ; i++)
{
if (i == n -1)
{
printf("%d",s[i] );
printf("->%d 距离为:%d\n",s[0],::distance [ s[n-1] ] [s[0]]);
printf("总距离是 %d\n",sum += ::distance [ s[n-1] ] [s[0]] );
break ;
}
printf("%d->%d 距离为:%d \n",s[i],s[i+1], ::distance[ s[i] ][ s[i+1] ] ;
sum += ::distance[ s[i] ][ s[i+1] ] ;
}
}
int main() {
TSP() ;
return 0;
}
这份代码我简单修改了一下,用pd变量解决了原来的数组溢出问题。
它的优点在于时间复杂度很低,在未被访问的节点中选择距离当前节点最近的节点作为下一个目标节点以此循环下去,这种算法在节点数很多的情况下能节省很多时间,可以很好地满足实时性的要求。缺点也很明显,它搜寻到的并不是全局最优路径,这个缺点在节点数不多的时候体现的不是很明显,但节点数和节点间距上来后会有一定的累积效应,可能会使结果与最优路径的差值很大。
第二种:
#include<iostream>
#include<cmath>
using namespace std;
#define N 9999
int distance[7][7] = {{10000,48,38,48,40,50,34}, // 城市间距离数组,10000表示自己到自己的距离
{48,10000,22,26,56,46,50},
{38,22,10000,28,46,40,40},
{48,26,28,10000,44,34,50},
{40,56,46,44,10000,22,26},
{50,46,40,34,22,10000,28},
{34,50,40,50,26,28,10000}};
//TSP问题求解函数
void TSP(int n,int** graph,int location){
//构建二维数组[i,2^n],j模拟集合个数
int **D = new int*[n+1]; //建行0~n,为方便理解和表示,第0行未使用
int **Rec = new int*[n+1]; //建立Rec
for(int i=1;i<=n;i++){
//建列表示集合
D[i] = new int [(int)pow(2,n)];
Rec[i] = new int [(int)pow(2,n)];
}
//初始化D、Rec
for(int i=1;i<=n;i++){
D[i][0]=graph[i][location]; //D[i,{}]
Rec[i][0]= -1;
for(int j=1;j<=(int)pow(2,n)-1;j++){
D[i][j]=N;
Rec[i][j] = -1;
}
}
//动态规划
for(int j=1;j<=(int)pow(2,n)-1;j++){ //对每一列求解
for(int i=1;i<=n;i++){ //每一行找最短路径
int min = N;
if(((int)pow(2,i-1) & j) == 0){ //顶点集不能包含i
int length = N;
for(int k=1;k<=n;k++){
if((int)pow(2,k-1) & j ){ //若顶点在集合中
length = graph[i][k] + D[k][j-(int)pow(2,k-1)];
if(length < min){
min = length;
D[i][j] = min;
Rec[i][j] = k;//局部最优决策
}
}
}
}
}
}
cout<<"最短长度:"<<D[location][(int)pow(2,n)-1-(int)pow(2,location-1)]<<endl;//最短路径长度
cout<<"最短路径为:"<<location;
int row = location;
int column = (int)pow(2,n)-1-(int)pow(2,row-1);
while(column > 0){
cout<< "->"<<Rec[row][column];
row = Rec[row][column];
column -= (int)pow(2,row-1);
}
cout<<"->"<<location<<endl;
}
int main(){
cout<<"旅行家需要游历多少个城市?:"<<endl;
int n;
n=7;
//建立二维数组模拟邻接矩阵
int **graph=new int* [n+1];
for(int i=1;i<=n;i++)
graph[i] = new int[n+1];
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
graph[i][j] = ::distance[i-1][j-1];
// cout<<"输入邻接矩阵graph["<<i<<"]["<<j<<"]的权:"<<endl;
// cin>>graph[i][j];
}
}
for(int i = 1 ; i <= 7 ; i++)
{
for(int j = 1 ; j <= 7 ; j++)
{
cout<<graph[i][j]<<" ";
}
cout<<endl;
}
cout<<"旅行家现在在第几个城市?"<<endl;
int location;
cin>>location;
TSP(n,graph,location); //TSP求解
return 0;
}
这个算法就比较经典,用了一个递推结构,不断将多节点的大距离拆散成小距离求解。
它的优点在于获取的会是全局最优路径,唯一欠佳的点就是他的时间复杂度达到了O(n*2^n),在多节点的情况下很难满足实时性要求,尤其是在运行过程中需要反复更新节点和路径的机器人。
网上也有一些其他的算法,在需要同时兼顾路径和实时的情况下,可以参考一下如模拟退火、粒子群等