问题描述
有n个城市,一个旅行者要访问所有的n个城市,最后回到起始点。城市之间的距离已知,求能够完成旅行的最短距离。
解决思路(分支限界法)
TSP问题的解空间是一颗排列树,每个节点为一个城市,记录了从树的根节点(即起始点)出发到该城市的一条路径,从节点选择一条未选择过的出边,从而产生新的节点。使用优先队列来存储节点,节点的优先级为该条路径最终费用的下界,这个下界为当前费用加上还没有经过的点的最小出边费用之和。在更新一个节点后,如果发现只差一个点就能够遍历全部点时,该节点的费用加上到达最后一个点的费用,再加上最后一个点到初始节点的费用,就是路径总费用,可以尝试更新最优解。当从优先级队列中取出的节点为叶子节点时,就找到了最优解。
具体实现时,每个节点要保存回路费用下界,到当前节点的费用,从根节点到当前节点的路径,以及从当前节点到最后一个节点,每个节点的最小出边的耗费之和。为了方便得到下界,在一开始就计算好所有城市的最小出边之和,每从一个城市离开(从一个节点产生新的节点),就用当前节点到最后一个节点的最小出边之和减去离开的那条出边费用。
实现如下:
1.定义节点结构,优先级队列,存储图的邻接矩阵,并读入数据。
2.计算每个城市的最小出边费用,以及所有城市的最小出边费用之和。
3.从初始节点开始遍历,搜索排列空间树。取出节点并进行扩展,如果遇到了叶子节点的父节点,尝试更新最优解,遇到了叶子节点则结束遍历。
4.从叶子节点得到最优解的路径。
核心代码
//定义邻接矩阵,最大城市数等
const int noEdge = -1;
const int N = 10;
int cost[N][N];
int bestPath[N];
//节点定义
struct Node{
int s; //已经走过的城市为[0:s]
int path[N]; //[0:s]记录已经走过的城市标号, [s:n-1]是未走过的城市标号
int currentCost; //到当前节点的总费用
int restMinCost; //剩余节点的最小出边费用之和
int bound; //从当前节点继续完成整个路径的费用下界
//下界小的优先级高
friend bool operator<(Node a, Node b){
return a.bound > b.bound;
}
};
//分支限界
int TSP(int n){
int bestCost; //结果
int currentCost, restMinCost, bound, minCost; //节点状态
//每个点的最小出路费用,所有点的最小出路费用
int minOutCost[n], sumMinCost;
//优先级队列
priority_queue<Node> Q;
//当前节点与子节点
Node curNode, childNode;
//计算最小出路费用
sumMinCost = 0;
for(int i=1;i<=n;i++){
minCost = noEdge;
for(int j=0;j<n;j++){
if(cost[i][j] != noEdge && (minCost == noEdge || minCost > cost[i][j])){
minCost = cost[i][j]; //如果有边且费用更小,更新minCost
}
}
if(minCost == noEdge) return noEdge; //如果一个点没有出边,则环路不存在
minOutCost[i] = minCost;
sumMinCost += minCost;
}
//初始化
bestCost = noEdge;
curNode.restMinCost = curNode.bound = sumMinCost;
curNode.currentCost = 0;
curNode.s = 0;
for(int i=0;i<n;i++){
curNode.path[i] = i+1;
}
//搜索排列树
while(curNode.s<n-1){
if(curNode.s == n-2){
//叶子节点的父节点,检查是否存在环路,并尝试更新最优解
int cost1, cost2;
cost1 = cost[curNode.path[n-2]][curNode.path[n-1]];
cost2 = cost[curNode.path[n-1]][1];
if(cost1 != noEdge && cost2 != noEdge && ((curNode.currentCost + cost1 + cost2) < bestCost || bestCost == noEdge )){
bestCost = curNode.currentCost + cost1 + cost2;
curNode.currentCost = bestCost;
curNode.bound = bestCost;
curNode.s++;
Q.push(curNode);
}
}
else{
//进行子节点的扩展
for(int i=curNode.s+1;i<n;i++){
//无法到达,跳过该点
if(cost[curNode.path[curNode.s]][curNode.path[i]] == noEdge) continue;
//对可到达的点进行扩展
currentCost = curNode.currentCost + cost[curNode.path[curNode.s]][curNode.path[i]];
restMinCost = curNode.restMinCost - minOutCost[curNode.path[curNode.s]];
bound = currentCost + restMinCost;
//通过下界判断是否可能产生最优解
if(bound < bestCost || bestCost ==noEdge){
childNode.s = curNode.s + 1;
childNode.currentCost = currentCost;
childNode.restMinCost = restMinCost;
childNode.bound = bound;
//更新路径
for(int i=0;i<n;i++){
childNode.path[i] = curNode.path[i];
}
childNode.path[childNode.s] = curNode.path[i];
childNode.path[i] = curNode.path[childNode.s];
Q.push(childNode);
}
}
}
if(Q.empty()) break;
curNode = Q.top();
Q.pop();
}
//没有回路
if(bestCost == noEdge) return noEdge;
//有回路则为最优解
for(int i=0;i<n;i++){
bestPath[i] = curNode.path[i];
}
return bestCost;
}