什么是旅行商问题
旅行商问题(TravelingSalesmanProblem,TSP)是一个经典的组合优化问题。经典的TSP可以描述为:一个商品推销员要去若干个城市推销商品,该推销员从一个城市出发,需要经过所有城市后,回到出发地。应如何选择行进路线,以使总的行程最短。
从图论的角度来看,该问题实质是在一个带权完全无向图中,找一个权值最小的Hamilton回路。由于该问题的可行解是所有顶点的全排列,随着顶点数的增加,会产生组合爆炸,它是一个NP完全问题。由于其在交通运输、电路板线路设计以及物流配送等领域内有着广泛的应用,国内外学者对其进行了大量的研究。早期的研究者使用精确算法求解该问题,常用的方法包括:分枝定界法、线性规划法、动态规划法等。但是,随着问题规模的增大,精确算法将变得无能为力,因此,在后来的研究中,国内外学者重点使用近似算法或启发式算法,主要有遗传算法、模拟退火法、蚁群算法、禁忌搜索算法、贪婪算法和神经网络等。
为什么用分支界限法?
为什么要用分支界限法(优先队列)来解决这个问题呢?因为分支界限(优先队列)本质上是一种暴力+技巧的一种算法,如果技巧十分的巧妙,那么求解的问题往往效果非常的好,另外和回溯法和暴力法不同的时候,在你优先队列的优先级确定的十分巧妙的时候,能够保证第一个出队列的叶节点它就是最优解(有解的情况下),这样能够大大减少计算的时间。
优先级的定义
这个优先级的定义稍微有点儿抽象,但你可以这样想,首先定义一个与顶点数量同样大的数组MinOut,它用来存放从该节点到达其他有路径结点的最小费用。再用MinSum计算这个最小费用数组的费用总和,可以确定这个总和一定是最小最小的费用,但是这个费用对应的路径不一定能够走得通,具体代码如下:
Type* MinOut = new Type[n + 1];
Type MinSum = 0; //最小出边费用和
for (int i = 1; i <= n; i++) {
Type Min = NoEdge;
for (int j = 1; j <= n; j++) {
if (a[i][j] != NoEdge && (a[i][j] < Min || Min == NoEdge)) //找出那个最小出边
Min = a[i][j];
}
if (Min == NoEdge) //不存在回路
return NoEdge;
MinOut[i] = Min; //MinOut是用来记录最小出边的
MinSum += Min; //最小出边费用和
}
知道MinSum的意义之后,接着这样想,假设s是我们正在或者刚到达的城市,假设从第s到第n个城市的最小费用用rcost定义,它的初始值为MinSum。每次到达一个城市,MinSum就减去s城市对应的最小费用即减去MinOut[s],并且这个计算是从第一个城市开始的,于是有:
rcost = E.rcost - MinOut[E.x[E.s]];
同时定义优先级为lcost,它指的是从第一个城市到第s个城市的费用(这是一个确定的值),它加上rcost这时候不就是走完回路的最小费用,而它就是这条路径的下界,也就是我们优先级的定义:
lcost = cc + rcost;//cc指的是到达s城市之前的费用
理解完以上的关键部分,有以下的代码:
#include <iostream>
#include <queue>
using namespace std;
template <class Type>
class Traveling {
//friend void main(void);
public:
Type BBTSP(int v[]);
int n; //图G的顶点数
Type** a, //图G的零阶矩阵
NoEdge; //图G的无边标志
//cc, //当前费用
//bestc; //当前最小费用
};
//最小堆表示活结点优先队列,最小堆的元素类型是MinHeapNode
template <class Type>
class MinHeapNode {
friend Traveling<Type>;
public:
Type lcost, //子树费用的下界
cc, //当前费用
rcost; //x[s:n-1]中顶点最小出边费用和
int s, //根节点到当前结点的路径为x[0:s]
* x; //需要进一步搜索的顶点是x[s+1:n-1]
bool operator <(const MinHeapNode& p)const
{
return p.lcost < lcost;//目标函数值小的先出队列
}
};
/*
堆中的每个结点lcost值是优先队列的优先级
每个顶点的最小费用出边用Minout记录
如果顶点没有出边,则不存在回路
*/
template <class Type>
Type Traveling<Type>::BBTSP(int v[]) {
priority_queue<MinHeapNode<Type>>H;
Type* MinOut = new Type[n + 1];
Type MinSum = 0; //最小出边费用和
for (int i = 1; i <= n; i++) {
Type Min = NoEdge;
for (int j = 1; j <= n; j++) {
if (a[i][j] != NoEdge && (a[i][j] < Min || Min == NoEdge)) //找出那个最小出边
Min = a[i][j];
}
if (Min == NoEdge) //不存在回路
return NoEdge;
MinOut[i] = Min; //MinOut是用来记录最小出边的
MinSum += Min; //最小出边费用和
}
//初始化
MinHeapNode <Type> E; //创建一个新节点
E.x = new int[n];
for (int i = 0; i < n; i++)
E.x[i] = i + 1;
E.s = 0; //走过的结点的数量
E.cc = 0; //当前费用初始化为0
E.rcost = MinSum;
Type bestc = NoEdge;
//搜索排列空间树
while (E.s < n - 1) { //非叶节点
if (E.s == n - 2) { //叶节点的父节点(排列问题嘛,还剩一个结点没排列的时候其实已经是固定的了)
if (a[E.x[n - 2]][E.x[n - 1]] != NoEdge && a[E.x[n - 1]][1] != NoEdge &&//更新的条件是,倒数第二个结点到最后一个结点有边,最后一个结点到出发点有边
(E.cc + a[E.x[n - 2]][E.x[n - 1]] + a[E.x[n - 1]][1] < bestc || bestc == NoEdge)) {//同时这个费用小于当前费用,或者bestc一直为无穷大
bestc = E.cc + a[E.x[n - 2]][E.x[n - 1]] + a[E.x[n - 1]][1];
E.cc = bestc;
E.lcost = bestc;
E.s++;
H.push(E);
}
else
delete[] E.x; //舍弃扩展结点
}
else {
for (int i = E.s + 1; i < n; i++) {//还剩(s,n)没有去过
if (a[E.x[E.s]][E.x[i]] != NoEdge) {//如果存在边,就成为可能的可行结点
Type cc = E.cc + a[E.x[E.s]][E.x[i]];
Type rcost = E.rcost - MinOut[E.x[E.s]];//每次走过一个城市,就减去对应的MinOut(这个城市到其他城市的最小费用),这里需要想想
Type b = cc + rcost; //得到下界
if (b < bestc || bestc == NoEdge) {//剪枝策略
MinHeapNode <Type> N;
N.x = new int[n];
for (int j = 0; j < n; j++)
N.x[j] = E.x[j];
//交换一下位置,因为这个位于i位置上的城市作为下一个去的城市,它可能产生最优解
N.x[E.s + 1] = E.x[i];
N.x[i] = E.x[E.s + 1];
N.cc = cc;
N.s = E.s + 1;
N.lcost = b;
N.rcost = rcost;
H.push(N);
}
}
}
delete[]E.x;//完成结点扩展
}
if (H.empty())
break;
E = H.top();
H.pop();
}
if (bestc == NoEdge)
return NoEdge;
for (int i = 0; i < n; i++) {//最优解复制到v
v[i + 1] = E.x[i];
}
while (true) {
delete[]E.x;
if (H.empty())
break;
E = H.top();
H.pop();
}
return bestc;
}
int main() {
Traveling <int> T;
cin >> T.n;
T.NoEdge = INT_MAX;
int* v = new int[T.n + 1];
T.a = (int**) new int* [T.n + 1];
for (int i = 0; i <= T.n; i++) {
T.a[i] = new int[T.n + 1];
}
for (int i = 1; i <= T.n; i++)
{
for (int j = 1; j <= T.n; j++)
{
cin >> T.a[i][j];
if (i == j)
{
T.a[i][j] = INT_MAX;
}
}
}
cout << T.BBTSP(v) << endl;
return 0;
}
/*测试
5
100000 5 61 34 12
57 100000 43 20 7
39 42 100000 8 21
6 50 42 100000 8
41 26 10 35 100000
36
请按任意键继续. . .
*/