蛮力法解 TSP 问题

蛮力法#

蛮力法也称穷举法或枚举法,是一种简单直接地解决问题的方法,常常直接基于问题的描述,所以蛮力法也是最容易应用的方法。蛮力法所依赖的基本技术是遍历,也称扫描,即采用一定的策略依次理待求解问题的所有元素,从而找出问题的解。依次处理所有元素是蛮力法的关键,为了避免陷人重复试探,应保证处理过的元素不再被处理。

TSP 问题#

TSP 问题(Traveling Salesman Problem)又译为旅行推销员问题、货郎担问题,是数学领域中著名问题之一。假设有一个旅行商人要拜访 n 个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求得的路径路程为所有路径之中的最小值。——百度百科

使用蛮力法求解 TSP 问题的思想是,通过穷举的方式把所有可能的路径找出来,然后对每一条路径都计算开销,最终找出开销最小的路径。例如对于如图 4 个城市的拓扑,使用蛮力法求解的过程如表格所示。

序号 路径 路径长度 是否最短
1 a->b->c->d->a 18
2 a->b->d->c->a 11
3 a->c->b->d->a 23
4 a->c->d->b->a 11
5 a->d->b->c->a 23
6 a->d->c->b->a 18

当城市规模增大时,存在的路径数会呈现指数型增长,例如 11 个城市的拓扑图如下所示。

实验程序编写#

图结构体定义#

TSP 是个 NP 完全问题,我们需要图结构来进行存储。我选择邻接矩阵存储城市拓扑图,定义的图结构体如下。

Copy Highlighter-hljs
typedef struct //图的定义 { int edges[MAXV][MAXV]; //邻接矩阵 int n; //顶点数 } MGraph;

城市拓扑的建立#

接下来就需要把城市拓扑存储在邻接矩阵中,因为城市拓扑是完全图,因此我们需要存储所有城市之间的距离。

Copy Highlighter-hljs
MGraph CreateMGraph(int num) //建图 { MGraph topography; for (int i = 1; i <= num; i++) { for (int j = 1; j <= num; j++) { topography.edges[i][j] = 0; } } for (int i = 1; i <= num; i++) { for (int j = i + 1; j <= num; j++) { printf("城市%d和城市%d之间的距离为:",i,j); cin >> topography.edges[i][j]; topography.edges[j][i] = topography.edges[i][j]; } } topography.n = num; return topography; }

DFS#

想要获取最短的路线,使用蛮力法进行分析时需要先获取所有的路径。DFS 可以获取所有的路径,编写的代码如下。注意当获取一条新路径时,需要先把该路径拷贝到下一个路径,因为递归实现的 DFS 无法返回上一层递归执行填充操作。这么做是可行的,因为相邻路径不需要回溯的路线是一样的,而回溯的部分会直接覆盖原来的路线。

Copy Highlighter-hljs
void DFS(int new_point, int cities_visited, int &path_index) //深度遍历 { count++; if (cities_visited == topography.n) //所有城市都走一遍 { path[path_index][cities_visited] = new_point; path[path_index][cities_visited + 1] = start_point; //回到出发点 for(int i = 1; i <= topography.n; i++) { path[path_index + 1][i] = path[path_index][i]; //下一条路径拷贝上一条 } path_index++; } else { for (int i = 1; i <= topography.n; i++) { if (visited[i] == 0) { visited[i] = 1; path[path_index][cities_visited] = new_point; DFS(i, cities_visited + 1, path_index); visited[i] = 0; //回溯到上一城市 } } } return; }

主函数#

接下来要计算所有路径的长度,并且得出最短的路线。同时我们也需要确定 DFS 执行了多少次,方便我们分析时间复杂度。

Copy Highlighter-hljs
int main() { int cities_num = 0; //城市数量 int path_num = 1; //路径数 int cities_visited = 1; //已访问城市数 int path_index = 1; //已获取的路径数 int min_path = 0; int min_sum = 9999999; int sum; cout << "城市数量为:"; cin >> cities_num; //建图 topography = CreateMGraph(cities_num); for(int i = cities_num - 1; i > 1; i--) { path_num *= i; } //初始化访问状态 for(int i = 1; i <= topography.n; i++) { visited[i] = 0; } //出发 cout << "从哪个城市出发:"; cin >> start_point; visited[start_point] = 1; //获取所有路径 DFS(start_point, cities_visited, path_index); //得出最短路径 ofstream outfile; outfile.open("11.txt"); for (int i = 1; i < path_index; i++) { sum = 0; outfile << "路径" << i << ":"; for (int j = 1; j <= cities_num; j++) { sum += topography.edges[ path[i][j] ][ path[i][j + 1] ]; } if(sum < min_sum) { min_sum = sum; min_path = i; } } cout << "\n最短路径为路径" << min_path << ":"; for (int j = 1; j <= cities_num; j++) { cout << path[min_path][j] << " -> "; outfile << path[min_path][j] << " -> "; sum += topography.edges[ path[min_path][j] ][ path[min_path][j + 1] ]; } cout << path[min_path][cities_num + 1] << endl; cout << "最短路径长度为:" << min_sum << endl; cout << "DFS 次数为:" << count; return 0; }

获取实验数据#

使用上述不同规模的城市拓扑分析TSP问题,得出的实验数据如下。

城市规模(个) 路线数(条) DFS次数(次) 数据文件大小(KB)
4 6 16 1
5 24 65 2
6 120 326 7
7 720 1957 45
8 5070 13700 348
9 40320 109601 3050
10 362880 986410 30260
11 3628800 9864101 330943



实验数据分析#

当使用蛮力法解决TSP问题时,需要考虑从某个城市出发的所有路线。由于城市之间彼此互通,城市拓扑是个完全图,因此所有路线的数量规模是 n-1 个城市的全排列。
当输入城市数量n时,会产生n!条路线,从而计算路径长度的操作就需要执行n!次。也就是说蛮力法解决TSP 问题的 T(n) = n!,从而得出 O(n) = n!。无论是路线数、DFS 次数还是数据文件大小,都能明显地体现这个趋势。

路线数#


DFS 次数#


数据文件大小#


总结#

我一开始使用的是 C++ 的 new 运算符动态内存分配二维数组。但是除了城市规模 4 的数据下,其他的规模均无法正常运行,并且主函数 “return value 3221225477”。经过查阅资料得知这可能和未初始化的变量或指针引发的,但是我并不知道问题代码及其原因,无奈之下只好直接定义了一个较大的二维数组进行存储。
在测试城市规模 12 的数据时,由于栈区空间已经用尽,我打算使用动态内存分配使用堆区内存。结果需要分配的内存过多,导致所有内存空间全部被 C++ 占用,而 C++ 并没有智能保护内存的机制,导致我的电脑直接宕机。在强行断电并修复电脑之后觉定放弃 12 个城市的 TSP 问题求解。
蛮力法是解决问题明确而直接的手法,程序编写较为简单,思路是模拟情景下的所有可能性进行分析,在时间允许下是极佳的算法。但是在不同的情景下会存在效率低下的情况,我们会需要更加巧妙的算法提高解决问题的效率,期待接下来对算法的进一步学习,使用其他的算法对这些问题进行求解。

参考资料#

《数据结构(C语言版|第二版)》—— 严蔚敏 李冬梅 吴伟民 编著,人民邮电出版社
《算法设计与分析(第二版)》——王红梅,胡明 编著,清华大学出版社
Graphviz 安装并使用 (Python)
C++文件和流
百度百科:TSP问题

posted @   乌漆WhiteMoon  阅读(3885)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示
CONTENTS