蛮力法解 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 完全问题,我们需要图结构来进行存储。我选择邻接矩阵存储城市拓扑图,定义的图结构体如下。

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

城市拓扑的建立

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

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 无法返回上一层递归执行填充操作。这么做是可行的,因为相邻路径不需要回溯的路线是一样的,而回溯的部分会直接覆盖原来的路线。

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 执行了多少次,方便我们分析时间复杂度。

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 @ 2020-10-26 21:34  乌漆WhiteMoon  阅读(3521)  评论(0编辑  收藏  举报