TSP_旅行商问题 - 蛮力法DFS(一)

一、前言

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

《TSP_旅行商问题 - 蛮力法DFS(一)》

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

《TSP_旅行商问题 - 蛮力法DFS(一)》

蛮力法求解上图的所有路径并记录最佳路径的相关信息

    旅行商求解系列:

————————————————————————————————-

(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_旅行商问题 - 蛮力法DFS(一)》

五、总结

    使用蛮力法解决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);	 // 深度优先遍历 - 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;	 // 最短路径初始化为无穷大

	// 功能函数
	void CreateGraph(Graph &G);
	int _findCityIndex(Graph G, char city_start);
	void DFS(Graph G, char city_start);

#endif  //_BP_H_ 

2. DFS.cpp:

#include <iostream>
#include <stdlib.h> 
#include <queue>
#include <stack>
#include <fstream>
#include <iomanip>	// 本文用于输出对齐

#include "DFS.h"

using namespace std;

ofstream DFS_fout("L:\\Coding\\图的常见操作\\图的常见操作\\city_10_out_2.txt");

int main(){
	cout<<"程序开始..."<<endl;
	time_t T_begin = clock();

	Graph G;
	CreateGraph(G);

	for (int i = 0; i < G.vex_num; i++)
	{
		is_visited[i] = false;
	}

	char city_start = 'A';
	DFS(G, city_start);


	for (int i = 0; i < path_num; i++)
	{
		for (int j = 0; j <= G.vex_num; j++)
		{
			// cout<<path_DFS[i][j]<<" ";
			// DFS_fout<<path_DFS[i][j]<<" ";
		}
		// cout<<"对应的路程lenth_DFS[] = "<<lenth_DFS[i]<<endl;
		// DFS_fout<<"对应的路程lenth_DFS[] = "<<lenth_DFS[i]<<endl;
	}

	// DFS_fout<<"最短路程bestLength = "<<bestLength<<endl;
	cout<<"最短路程bestLength = "<<bestLength<<endl;

	time_t T_end = clock();
	double RunningTime = double(T_end - T_begin)/CLOCKS_PER_SEC;
	// DFS_fout<<"程序运行时间 RunningTime = "<<RunningTime<<endl;
	cout<<"程序运行时间 RunningTime = "<<RunningTime<<endl;
	system("pause");
	return 0;
}

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;
	}
}

int _findCityIndex(Graph G, char city_start){
	for (int i = 0; i < G.vex_num;i++)
	{
		if (G.vexs[i] == city_start)
		{
			return i;
		}
	}
	cout<<"【error】当前城市未找到!"<<endl;
	return -1;
}

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];
		}

		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));
				path_index--;
				is_visited[v_index] = false;
			}
		}
	}

}

七、资源下载

    原文作者:DFS
    原文地址: https://blog.csdn.net/houchaoqun_xmu/article/details/54577439
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞