zoukankan      html  css  js  c++  java
  • 蛮力法解 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 << "
    最短路径为路径" << 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问题

  • 相关阅读:
    《黑马程序员》 内存管理的认识(Objective
    《黑马程序员》 description方法(Objective
    《黑马程序员》 类的加载和初始化(Objective
    《黑马程序员》 category分类的使用(Objective
    《黑马程序员》 OC构造方法(Objective
    《黑马程序员》 OC编译器特性(Objective
    《黑马程序员》 OC的三大特性(Objective
    《黑马程序员》 OC的认识和第一个OC(Objective
    《黑马程序员》 extern与static的使用注意(C语言)
    《黑马程序员》 typedef的使用方法(C语言)
  • 原文地址:https://www.cnblogs.com/linfangnan/p/13839100.html
Copyright © 2011-2022 走看看