旅行商问题,又称旅行者,推销员问题,货郎担问题。该问题十分经典,几十年前用于计算机国际竞赛,现在被用来作为算法的入门练习。
假设有一个商人要拜访n个城市,他必须走完所有路径,且路径不能重复,并且走到最后能回到原来路径。
事实上我们可以将这个问题转换成一张图(丑了点)
注:这篇文章是对无向路径进行求解,并非双向,又称为修路问题以及最短路径问题。
该题只为TSP问题的一个特例,核心是——最小生成树问题
例如这样:
可以画出它的邻接矩阵:
类似这样:
邻接矩阵中对应位置写出与自己相连的点距离值
0代表当前旅行商的位置,那么问题就转换成了,我要如何走这张图,才能让我走完每个点,并且不能重复过点,而且使得最终路径最短。
以上三个要求其实就是解决问题的入题点,无论使用动态规划还是蛮力法都只是对上面三个条件的求解。
这样的题目我们通常采用的方法是将其转换成可以用数学方式解答的问题。
那么我们常用的手段就是图和树:
这道题是一个点——线问题,那么用图可以将问题抽象出来
我们将图一转换成一个邻接矩阵 邻接矩阵可以将点和边的关系很好的展现出来
我们将点装进集合,走过一个点,就把它从集合中剔除,这样可以保证不走重复路径。
将走过的路径装进一个数组中,每次走了一条路,就把路径与之前走过的路径相加。
那么这个问题就可以分为三个步骤:
- 初始化点的集合和走过的路径长度
- 寻找最近的没走过的点
- 将这个点从集合中剔除,并将路径与原来路径相加,表示走过了这条路。
下面是C++的源代码
#include<iostream>
using namespace std;
const int INF = 0x3ffffffff;
const int N = 100;
bool s[N];
int closest[N];
int lowcost[N];
void Prim(int n, int u0, int c[N][N])
{
//顶点个数,开始顶点,带权矩阵
//如果s[i]=true,说明顶点i属于句话V-U
//将最后的相关的最小权值传递到数组lowcost
s[u0] = true;//最初时,集合中U只有一个元素,即顶点u0
int i;
int j;
for (i = 1; i <= n; i++) {
if (i != u0) {
lowcost[i] = c[u0][i];
closest[i] = u0;
s[i] = false;
}
else {
lowcost[i] = 0;
}
for (i = 1; i <= n; i++) {
int temp = INF;
int t = u0;
for (j = 1; j <= n; j++) {
if ((!s[j]) && (lowcost[j] < temp))
{
t = j;
temp = lowcost[j];
}
}
if (t == u0) {
break;
}
s[t] = true;
for (j = 1; j <= n; j++) {
if ((!s[j]) && (c[t][j] < lowcost[j]))
{
lowcost[j] = c[t][j];
closest[j] = t;
}
}
}
}
}
int main() {
int n, c[N][N], m, u, v, w;
int u0;
cout << "输入结点数n和边数m:" << endl;
cin >> n >> m;
int sumcost = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
c[i][j] = INF;
}
}
cout << "输入结点数u,v和边值w: " << endl;
for (int i = 1; i <= m; i++) {
cin >> u >> v >> w;
c[u][v] = c[v][u] = w;
}
cout << "输入任一结点u0: " << endl;
cin >> u0;
Prim(n, u0, c);
cout << "数组lowcost的内容为: " << endl;
for (int i = 1; i <= n; i++) {
cout << lowcost[i] << " ";
}
cout << endl;
for (int i = 1; i <= n; i++) {
sumcost += lowcost[i];
}
cout << "最小的花费是:" << sumcost << endl << endl;
return 0;
}