TSP问题
问题描述
在一个具有n个城市的完全图中,旅行者希望进行一次巡回旅行,或经历一次哈密顿
回路,可以恰好访问每一个城市一次,并且最终回到出发城市。而这次巡回旅行的
总费用为访问各个城市费用的总和,故旅行者同时希望整个行程的费用是最低的,
求这个路线的排列策略?
TSP问题可以抽象为
在一个带权重的完全无向图中,找到一个权值总和最小的哈密顿回路
显然,TSP问题的组合解有N!种组合,随着城市数量N的规模增加,组合数将呈指数级别递增,故使用穷举法将会面临组合爆炸问题,因此TSP属于NP完全问题
解决方案
常用的方法包括:分枝定界法、线性规划法、动态规划法等。但是,随着问题规模的增大,精确算法将变得无能为力,因此,在后来的研究中,国内外学者重点使用近似算法或启发式算法,主要有遗传算法、模拟退火法、蚁群算法、禁忌搜索算法、贪婪算法和神经网络等。
提示:以下代码能稳定得出结果的是动态规划,如果您想要了解能够得到最小代价路线的算法,建议您直接查看动态规划。
近似算法
参考
费用函数也叫代价函数,指的是两个城市之间的费用指数或者代价程度的量化。在
大多数的实际情况中,从一个地方u直接到另一个地方w,这个走法花费的代价总是
最小的,而如果从u到w需要经过某个中转站v,则这种走法花费的代价却不可能比直
接到达的走法花费的代价更小
数学语言描述:
C ( u , w ) ≤ C ( u , v ) + C ( v , w ) C(u, w) \leq C(u, v) + C(v, w) C(u,w)≤C(u,v)+C(v,w)
c是费用函数,这个方程说明了,直接从u->w
花费的代价,要比从u->v->w
花费的代价要小,我们称这个费用函数满足三角不等式
方案步骤(Prim)
(1) 选择G的任意一个顶点r作为根节点(出发点/终点)
(2) 用Prim算法找出G的一颗最小生成树T
(3) 前序遍历访问树T,得到顺序组成的顶点表L
(4) 将r加入顶点表L的末尾,按L中顶点的次序组成哈密顿回路H
当费用函数满足三角不等式时,上述近似算法找出的哈密顿回路产生的总费用,不会超过最优回路的2倍
性质描述
存储结构:
矩阵中的每一行代表G中每一个的顶点到其余各个顶点的费用(欧式距离),如果出现到达不了或者自身到达自身的情况,我们用无穷大inf来填充表示不可达(案例以邻接矩阵作为存储结构),后面的测试代码,部分用的是0表示不可达。
Prim算法:
在了解Prim算法之前,首先要知道什么是生成树,什么是最小生成树。
连通图:
在图中任一顶点都能到另一顶点至少存在一条通路
生成树:
指满足以下条件的连通图:
1. 包含图中所有的顶点
2. 任意顶点之间有且仅有一条通路
对一个连通图而言,它的生成树可能有很多种。假如此时的图中每一条边都代表了不同的权值,那么对不同的生成树,其对应的权值可能不同。
最小生成树:
在联通图中找到对应权值最小的生成树
Prim算法:
1. 将联通图的顶点分为两类:A类、B类。初始状态下,所有顶点位于B类,A类为空
2. 选择任一顶点,将其从B类移动到A类
3. 从B类的所有顶点出发,找到一条连接着A类中的某个顶点且权值最小的边,将此边连接着A类中的顶点从B类移动到A类
4. 重复第3步,直到B类中的所有顶点全部移动到A类,恰好可以找到N-1条边,结果从结构上是一颗二叉树(每个点度数最多只有2)。
提示
代码中添加了一些没有必要的功能,请读者查看时优先或者只看代码上方提示的主要算法部分,以节省您的时间,祝您生活愉快!
测试1-近似算法(Prim)
算法思想
- 采用Prim算法扫面原图创建生成树
- 深度遍历生成树得到一条路径
- 再将最后一个节点与起点相连,就得到一条回路,但并不是最优解。
得到的结果近似最优解,因此这种解法称为近似解法。
输入样例
第一行输入顶点数points
,第二行输入边数edges
,随后edges
行,每行输入u v w
(u,v
表示顶点,w
表示u
到v
的路径权值)
Please enter the vertex order of the Graphs:
The Points: 6
The Edges: 15
Please enter the adjacency matrix of Graph:
0 1 5
0 2 1
0 3 6
1 2 4
2 3 5
2 4 6
2 5 4
1 5 2
3 4 3
4 5 6
0 4 7
0 5 7
1 3 7
1 4 7
3 5 7
输出结果
输出原连通图的邻接矩阵,生成树的邻接矩阵,从起点到终点的路径,路径总代价
Print the adjacency matrix:
0 5 1 6 7 7
5 0 4 7 7 2
1 4 0 5 6 4
6 7 5 0 3 7
7 7 6 3 0 6
7 2 4 7 6 0
Print the adjacency matrix:
0 0 1 0 0 0
0 0 4 0 0 2
1 4 0 5 0 0
0 0 5 0 3 0
0 0 0 3 0 0
0 2 0 0 0 0
The RePath_N :
[(0)]---1-->[(2)]---5-->[(3)]---3-->[(4)]---7-->[(1)]---2-->[(5)]---7-->[(0)]
Total Cost: 25
测试代码
算法核心位置:Graph getShortPath_ByPrim(int u_id)、vector getPath_ByDFS(int u_id)
#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>
#include <cstring>
using namespace std;
const int inf = INT_MAX;
class Graph
{
private:
int points;
int edges;
vector<vector<int>> Matrix; // 邻接矩阵
public:
// 邻接矩阵、顶点度数初始化
Graph(int points, int edges = 0);
// 设置顶点数
void setPoints(int points);
// 获取顶点数
int getPoints();
// 设置边数
void setEdges(int edges);
// 获得边数
int getEdges();
// 打印邻接矩阵
void Print_Matirx();
// 输入邻接矩阵
void setMatrix();
// 往图中加入新的边
void add_edge(int u, int v, int w);
// 生成最小生成树,返回生成树的邻接矩阵(Prim算法)
Graph getShortPath_ByPrim(int u_id);
// 深度遍历生成树
vector<int> getPath_ByDFS(int u_id);
// 打印回路
void Print_RePath_N(int u_id, vector<int> &res);
};
Graph::Graph(int points, int edges)
{
// memset(Matrix, 0, sizeof(Matrix)); // 来自<cstring>
this->points = points;
this->edges = edges;
vector<vector<int>> tmp(points, vector<int>(points, inf));
Matrix = tmp;
}
void Graph::setPoints(int points)
{
this->points = points;
}
int Graph::getPoints()
{
return points;
}
void Graph::setEdges(int edges)
{
this->edges = edges;
}
int Graph::getEdges()
{
return edges;
}
void Graph::Print_Matirx()
{
cout << "Print the adjacency matrix: " << endl;
int i, j;
for (i = 0; i < points; i++)
{
for (j = 0; j < points - 1; j++)
{
if (Matrix[i][j] == inf)
cout << 0 << " ";
else
cout << Matrix[i][j] << " ";
}
if (Matrix[i][j] == inf)
cout << 0 << endl;
else
cout << Matrix[i][j] << endl;
}
}
void Graph::setMatrix()
{
int u, v, weight;
for (int i = 0; i < this->edges; i++)
{
cin >> u >> v >> weight;
Matrix[u][v] = weight;
Matrix[v][u] = weight;
}
}
void Graph::add_edge(int u, int v, int w)
{
Matrix[u][v] = w;
Matrix[v][u] = w;
}
Graph Graph::getShortPath_ByPrim(int u_id)
{
Graph res(points);
bool visit[points]; // 默认为false
vector<int> lowcost(points, inf); // 存放点与点间最小权值,会不断更新
vector<int> parent(points, -1); // 记录路径(当前节点前一个节点的坐标)
int edges = 0; // 统计生成树的边数
lowcost[u_id] = 0; // 默认认为从无到第一个顶点u_id的距离为0
for (int i = 0; i < points; i++)
{
int minlen = inf;
int u = -1;
for (int j = 0; j < points; j++)
{
if (lowcost[j] < minlen && !visit[j])
{
minlen = lowcost[j];
u = j;
}
}
if (u == -1)
break;
visit[u] = true;
int v = parent[u];
if (v != -1)
{
res.add_edge(u, v, minlen);
edges++;
}
for (int j = 0; j < points; j++)
{