一、前言
【旅行商问题】旅行商问题(TravelingSalesmanProblem,TSP)是一个经典的组合优化问题。经典的TSP可以描述为:一个商品推销员要去若干个城市推销商品,该推销员从一个城市出发,需要经过所有城市后,回到出发地。应如何选择行进路线,以使总的行程最短。从图论的角度来看,该问题实质是在一个带权完全无向图中,找一个权值最小的Hamilton回路。由于该问题的可行解是所有顶点的全排列,随着顶点数的增加,会产生组合爆炸,它是一个NP完全问题。由于其在交通运输、电路板线路设计以及物流配送等领域内有着广泛的应用,国内外学者对其进行了大量的研究。早期的研究者使用精确算法求解该问题,常用的方法包括:分枝定界法、线性规划法、动态规划法等。但是,随着问题规模的增大,精确算法将变得无能为力,因此,在后来的研究中,国内外学者重点使用近似算法或启发式算法,主要有遗传算法、模拟退火法、蚁群算法、禁忌搜索算法、贪婪算法和神经网络等。【参考百度百科】。

旅行商问题:字母代表城市,数字代表城市间的路径或者费用

蛮力法求解上图的所有路径并记录最佳路径的相关信息
旅行商求解系列:
-------------------------------------------------------------------------------------------------
(1)TSP_旅行商问题- 蛮力法( 深度遍历优先算法DFS )
(2)TSP_旅行商问题- 动态规划
(3)TSP_旅行商问题- 模拟退火算法
(4)TSP_旅行商问题- 遗传算法
(5)TSP_旅行商问题- 粒子群算法
(6)TSP_旅行商问题- 神经网络
-------------------------------------------------------------------------------------------------
二、本文概要
本文基于蛮力法(此处采用深度优先遍历,DFS)解决旅行商问题。通过遍历出所有满足条件的路径情况,并保持更新最优解,直到所有情况都遍历完,得到全局最优解。但是,使用蛮力法需要遍历的城市个数高达city_num的阶乘,当city_num=12的时候,需遍历479001600种情况,程序所需时间以小时为单位。
三、蛮力法 - 深度优先遍历,DFS
1. 创建图的邻接矩阵:参考文章“图的存储方式”
-
void CreateGraph(Graph &G){
-
ifstream read_in;
-
read_in.open("L:\\Coding\\图的常见操作\\图的常见操作\\city_10.txt");
-
if (!read_in.is_open())
-
{
-
cout<<"文件读取失败."<<endl;
-
return;
-
}
-
-
read_in >> G.vex_num;
-
-
G.arc_num = 0;
-
for (int i = 0;i < G.vex_num; i++)
-
{
-
read_in >> G.vexs[i];
-
}
-
G.vexs[G.vex_num] = '\0'; // char的结束符.
-
-
for (int i = 0; i < G.vex_num;i++)
-
{
-
for (int j = 0; j < G.vex_num; j++)
-
{
-
read_in >> G.arcs[i][j];
-
-
// calculate the arc_num
-
if (G.arcs[i][j] > 0)
-
{
-
G.arc_num++;
-
}
-
}
-
}
-
-
// display
-
cout<<"无向图创建完毕,相关信息如下:"<<endl;
-
cout<<"【顶点数】 G.vex_num = "<<G.vex_num<<endl;
-
cout<<"【边数】 G.arc_num = "<<G.arc_num<<endl;
-
cout<<"【顶点向量】 vexs[max_vexNum] = ";
-
for (int i = 0; i < G.vex_num; i++)
-
{
-
cout<<G.vexs[i]<<" ";
-
}
-
cout<<endl<<"【邻接矩阵】 arcs[max_vexNum][max_vexNum] 如下:"<<endl;
-
for (int i = 0; i < G.vex_num;i++)
-
{
-
for (int j = 0; j < G.vex_num; j++)
-
{
-
cout << std::right<<setw(10) << G.arcs[i][j]<<" ";
-
}
-
cout<<endl;
-
}
-
}
2.
DFS - 递归
-
void DFS(Graph G, char city_start){
-
int v_index = _findCityIndex(G, city_start); // 起始城市,每次调用(递归)都更新.
-
-
if (path_index == G.vex_num - 1 && G.arcs[v_index][int('A') - 65] > 0)
-
{
-
path_DFS[path_num][path_index] = city_start;
-
path_DFS[path_num][path_index + 1] = 'A'; // A为起始城市
-
lenth_DFS[path_num] = 0; // 存储最短路径
-
-
// 计算最短路径
-
for (int i = 0; i < G.vex_num; i++)
-
{
-
lenth_DFS[path_num] += G.arcs[(int)path_DFS[path_num][i] - 65][(int)path_DFS[path_num][i+1] - 65];
-
}
-
-
if (bestLength > lenth_DFS[path_num])
-
{
-
// 更新最短路径
-
bestLength = lenth_DFS[path_num];
-
}
-
-
//cout << "第【" << (path_num + 1) << "】条路径!" << endl;
-
DFS_fout << "第【" << (path_num + 1) << "】条路径!" << endl;
-
path_num++; // 下一条路径
-
// 初始化下一次路径与上一次相同
-
for (int i = 0; i < G.vex_num;i++)
-
{
-
path_DFS[path_num][i] = path_DFS[path_num-1][i];
-
}
-
return;
-
}
-
else
-
{
-
for (int i = 0; i < G.vex_num; i++)
-
{
-
if (G.arcs[v_index][i] > 0 && !is_visited[i])
-
{
-
path_DFS[path_num][path_index] = city_start;
-
path_index++;
-
is_visited[v_index] = true;
-
DFS(G, (char)(i + 65));
-
// cout<<"--- 深度遍历回溯 ---"<<endl;
-
path_index--;
-
is_visited[v_index] = false;
-
}
-
}
-
}
-
}
三、算法分析(DFS)
1. DFS的时间复杂度:
1)采用邻接表表示的图深度遍历 -----》 时间复杂度为O(n+e);
2)采用邻接矩阵表示的图深度遍历 -----》 时间复杂度为O(n^2);
2. 空间复杂度:O(MAX_PATH_LENGTH * max_vexNum)
-
bool is_visited[max_vexNum]; // 存储当前城市是否已被访问
-
char path_DFS[MAX_PATH_LENGTH][max_vexNum]; // 存储所有路径
-
double lenth_DFS[MAX_PATH_LENGTH]; // 存储所有路径对应的长度
四、 测试数据及其结果
1.
程序数据:city_10.txt
-
10
-
A B C D E F G H I J
-
0 2538.94 2873.8 2575.27 2318.1 2158.71 2216.58 3174.04 3371.13 3540.24
-
2538.94 0 1073.54 111.288 266.835 395.032 410.118 637.942 853.554 1055
-
2873.8 1073.54 0 964.495 988.636 1094.32 1382.73 1240.15 1460.25 1687
-
2575.27 111.288 964.495 0 262.053 416.707 503.563 624.725 854.916 1068.42
-
2318.1 266.835 988.636 262.053 0 163.355 395.14 885 1110.86 1318.19
-
2158.71 395.032 1094.32 416.707 163.355 0 338.634 1030.34 1248.58 1447.69
-
2216.58 410.118 1382.73 503.563 395.14 338.634 0 984.068 1160.26 1323.7
-
3174.04 637.942 1240.15 624.725 885 1030.34 984.068 0 243.417 473.768
-
3371.13 853.554 1460.25 854.916 1110.86 1248.58 1160.26 243.417 0 232.112
-
3540.24 1055 1687 1068.42 1318.19 1447.69 1323.7 473.768 232.112 0
2. 程序运行结果
五、总结
使用蛮力法解决TSP问题的优缺点如下:
1. 优点:
1)在时间允许的条件下,一定能够得到全局最优解。
2)结构简单,易于实现。
3)暂用内存较少。
2. 缺点:
1)速度慢。
2)组合爆炸问题:当城市个数达到12的时候,需遍历479001600种路径,程序耗时以“小时”为单位。
3)不适用于大规模数据中,具体问题具体分析。
六、源程序
1. DFS.h:
-
#ifndef _DFS_H_
-
#define _DFS_H_
-
/* 1. 图 - 邻接矩阵表示法 */
-
/* ---------------------------------------------------------------- */
-
/* 较完善的数据结构
-
#define VRType int
-
#define InfoType int
-
#define VertexType char
-
#define max_n 20
-
typedef enum{DG, DN, AG, AN} GraphKind;
-
-
// 弧结点与矩阵的类型
-
typedef struct {
-
VRType adj; //VRType为弧的类型。图--0,1;网--权值
-
InfoType *Info; //与弧相关的信息的指针,可省略
-
}ArcCell, AdjMatrix[max_n][max_n];
-
-
// 图的类型
-
typedef struct{
-
VertexType vexs[max_n]; // 顶点向量
-
AdjMatrix arcs; // 邻接矩阵
-
int vexnum, arcnum; // 顶点数,边数
-
GraphKind kind; // 图类型
-
}MGraph;
-
*/
-
/* ---------------------------------------------------------------- */
-
-
/* 简化的数据结构 */
-
#define max_vexNum 26 // 最大城市个数
-
#define MAX_PATH_LENGTH 9999999
-
typedef struct{
-
int vex_num, arc_num; // 顶点数 边数
-
char vexs[max_vexNum]; // 顶点向量
-
double arcs[max_vexNum][max_vexNum]; // 邻接矩阵
-
}Graph;
-
-
void CreateGraph(Graph &G);
-
void DFS_Traverse(Graph G);
-
void DFS(Graph G, char city_start); <span style="white-space:pre;"> </span>// 深度优先遍历 - stack
-
void BFS(Graph G); // 广度优先遍历 - queue
-
-
bool is_visited[max_vexNum]; // 存储当前城市是否已被访问
-
char path_DFS[MAX_PATH_LENGTH][max_vexNum]; // 存储所有路径
-
double lenth_DFS[MAX_PATH_LENGTH]; // 存储所有路径对应的长度
-
-
long int path_num = 0, path_index = 0;
-
long double bestLength = INT_MAX + 0.0; <span style="white-space:pre;"> </span>// 最短路径初始化为无穷大
-
-
// 功能函数
-
void CreateGraph(Graph &G);
-
int _findCityIndex(Graph G, char city_start);
-
void DFS(Graph G, char city_start);
-
-
#endif //_BP_H_
2. DFS.cpp:
七、资源下载