问题描述
给定一个n顶点网络(有向或无向),找出一个包含n个顶点且具有最小耗费的换路。任何一个包含网络所有顶点的换路称为一个旅行。旅行商问题(Traveling Salesman Problem,TSP)是要寻找一条耗费最少的旅行。
图1 四顶点网络
如图1是一个四顶点无向网络。这个网络的一些旅行:1,2,4,3,1;1,3,2,4,1和1,4,3,2,1。旅行1,2,4,3,1的耗费为66,;旅行1,3,2,4,1的耗费为25;旅行1,4,3,2,1的耗费为55.故1,3,2,4,1是网络中耗费最少的旅行。
算法设计
和回溯法解决TSP问题一样,首先确定问题的解空间是一个排列树,从顶点1出发最后回到顶点1,所以排列可以表示为[1,x2,…,xn,1]。和子集树一样,使用一个优先级队列求解。要求旅行的耗费最小,故使用小根堆储存活动节点,堆中每个活动节点的子树耗费所能取得的下界lowerCost
是优先级队列的优先级,即当前节点确定的途径下继续完成排列能取得最低耗费(并不一定保证能最终达到这个值)。
每个活动节点是小根堆的一个元素,元素结构包括:
lowerCost; //当前节点往后排列,整个回路所能取得的总耗费的下限
currentCost; //从根到当前节点的当前排列的耗费
restMinCost; //从当前节点到最后一个节点,每个节点的最小出边的耗费之和
s; //从根节点到当前节点的路径为[0:s]
currentTour[]; //从根节点到当前节点的路径(排列)
如何获得lowerCost
:
对于每个活动节点,可知当前耗费currentCost
,对于剩余的顶点,计算每个顶点的最小出边之和restMinCost
即为剩余路径的最小耗费的下界,lowerCost=currentCost + restMinCost
。
算法步骤
1、准备工作:建立小根堆,用于存储活动节点。计算每个顶点的最小出边,若存在某个顶点没有出边,则算法终止。初始化树根(顶点1)为第一个活动节点。
2、判断节点是否是叶结点的父节点:是的话,则检查是否一定有最低耗费,若是加入小根堆;
3、不是叶结点的父节点,则生成子节点,并判断子节点是否有可能取得最低耗费,若可能则加入小根堆;
4、取出下一个节点作为活动节点,若该节点已经是叶结点,返回当前最低耗费值,即为最优旅行。若不是叶结点则循环2、3步。
图2 四顶点网络的解空间树
小根堆中的元素变化:{}——>{C,D,E}——>{C,D,K,J}——>{C,K,J,H,I}——>{C,K,J,I,N}——>{C,K,I,N}——>{C,K,I}
最后取出活动节点N,但是N已经是叶结点,故返回最优值25,和最佳途径1->3->2->4->1,并终止算法。
事实上,检查节点J的子节点P时执行的是步骤2,也能得到最优值25,但是由于N已经加入小根堆,并且当前最优值已经是25,所以P没有加入小根堆,途径1->4->2->3->1也就被排除了。
需要注意的是,对于以下的C++程序,需要手动释放掉每个活动节点的数据成员currentTour
数组的内寸,包括最后小根堆中剩余的节点。
C++实现
小根堆minHeap
元素结构
#pragma once
struct heapNode
{
int lowerCost; //当前节点往后排列,整个回路所能取得的总耗费的下限
int currentCost; //从根到当前节点的当前排列的耗费
int restMinCost; //从当前节点到最后一个节点,每个节点的最小出边的耗费之和
int s; //从根节点到当前节点的路径为[0:s]
int *currentTour; //从根节点到当前节点的路径(排列)
//算术运算时的类型转换
operator int() { return lowerCost; }
//重载大于号运算符,用于小根堆比较
bool operator>(const heapNode &right)
{
return lowerCost > right.lowerCost;
}
};
小根堆minHeap
部分源码
#pragma once
#include<iostream>
#include <algorithm>
using namespace std;
template<class T>
void changeLength1D(T*& a, int oldLength, int newLength)
{
if