采用贪心法求得近似解为1→3→5→4→2→1,其路径长度为1+2+3+7+3=16,这可以作为TSP问题的上界。
把矩阵中每一行最小的元素相加,可以得到一个简单的下界,其路径长度为1+3+1+3+2=10,但是还有一个信息量更大的下界:考虑一个TSP问题的完整解,在每条路径上,每个城市都有两条邻接边,一条是进入这个城市的,另一条是离开这个城市的,那么,如果把矩阵中每一行最小的两个元素相加再除以2,如果图中所有的代价都是整数,再对这个结果向上取整,就得到了一个合理的下界。
lb=((1+3)+(3+6)+(1+2)+(3+4)+(2+3))/2=14
于是,得到了目标函数的界[14, 16]。
需要强调的是,这个解并不是一个合法的选择(可能没有构成哈密顿回路),它仅仅给出了一个参考下界。
部分解的目标函数值的计算方法
例如图所示无向图,如果部分解包含边(1, 3,5),则该部分解的下界是lb=(2*(1+2)+3+3+(3+6)+(3+4))/2=14。
应用分支限界法求解图9.4所示无向图的TSP问题,其搜索空间如图9.5所示,具体的搜索过程如下(加黑表示该路径上已经确定的边):
(1)在根结点1,根据限界函数计算目标函数的值为lb=((1+3)+(3+6)+(1+2)+(3+4)+(2+3))/2=14;
(2)在结点2,从城市1到城市2,路径长度为3,目标函数的值为(23)+1+6+(1+2)+(3+4)+(2+3))/2=14,将结点2加入待处理结点表PT中;在结点3,从城市1到城市3,路径长度为1,目标函数的值为(21+1+1+(3+6)+(3+4)+(2+3))/2=14,将结点3加入表PT中;在结点4,从城市1到城市4,路径长度为5,目标函数的值为((2*5)+1+3+(3+6)+(1+2)+(2+3))/2=16,将结点4加入表PT中;在结点5,从城市1到城市5,路径长度为8,目标函数的值为((1+8)+(3+6)+(1+2)+(3+5)+(2+8))/2=19,超出目标函数的界,将结点5丢弃;
(3)在表PT中选取目标函数值极小的结点2优先进行搜索;
(4)在结点6,从城市2到城市3,目标函数值为((1+3)+(3+6)+(1+6)+(3+4)+(2+3))/2=16,将结点6加入表PT中;在结点7,从城市2到城市4,目标函数值为((1+3)+(3+7)+(1+2)+(3+7)+(2+3))/2=16,将结点7加入表PT中;在结点8,从城市2到城市5,目标函数值为
((1+3)+(3+9)+(1+2)+(3+4)+(2+9))/2=19,超出目标函数的界,将结点8丢弃;
(5)在表PT中选取目标函数值极小的结点3优先进行搜索;
(6)在结点9,从城市3到城市2,目标函数值为((1+3)+(3+6)+(1+6)+(3+4)+(2+3))/2=16,将结点9加入表PT中;在结点10,从城市3到城市4,目标函数值为((1+3)+(3+6)+(1+4)+(3+4)+(2+3))/2=15,将结点10加入表PT中;在结点11,从城市3到城市5,目标函数值为((1+3)+(3+6)+(1+2)+(3+4)+(2+3))/2=14,将结点11加入表PT中;
(7)在表PT中选取目标函数值极小的结点11优先进行搜索;
(8)在结点12,从城市5到城市2,目标函数值为((1+3)+(3+9)+(1+2)+(3+4)+(2+9))/2=19,超出目标函数的界,将结点12丢弃;在结点13,从城市5到城市4,目标函数值为((1+3)+(3+6)+(1+2)+(3+4)+(2+3))/2=14,将结点13
加入表PT中;
(9)在表PT中选取目标函数值极小的结点13优先进行搜索;
(10)在结点14,从城市4到城市2,目标函数值为((1+3)+(3+7)+(1+2)+(3+7)+(2+3))/2=16,最后从城市2回到城市1,目标函数值为((1+3)+(3+7)+(1+2)+(3+7)+(2+3))/2=16,由于结点14为叶子结点,得到一个可行解,其路径长度为16;
(11)在表PT中选取目标函数值极小的结点10优先进行搜索;
(12)在结点15,从城市4到城市2,目标函数的值为((1+3)+(3+7)+(1+4)+(7+4)+(2+3))/2=18,超出目标函数的界,将结点15丢弃;在结点16,从城市4到城市5,目标函数值为((1+3)+(3+6)+(1+4)+(3+4)+(2+3))/2=15,将结点16加入表PT中;
(13)在表PT中选取目标函数值极小的结点16优先进行搜索;
(14)在结点17,从城市5到城市2,目标函数的值为((1+3)+(3+9)+(1+4)+(3+4)+(9+3))/2=20,超出目标函数的界,将结点17丢弃;
(15)表PT中目标函数值均为16,且有一个是叶子结点14,所以,结点14对应的解1→3→5→4→2→1 即是TSP问题的最优解,搜索过程结束。
/*分支限界法求解TSP问题
*/
#include
#include
#include
#define MAXLENGTH 10
using namespace std;
//城市个数
int n;
//城市距离代价矩阵
int value[MAXLENGTH][MAXLENGTH];
//定义访问标识符
bool dfs_visited[MAXLENGTH];
//定义函数上界
int up;
//定义下界
int down;
//距离修正函数声明
void Modify();
//分支限界法求解TSP问题
int solve();
//贪心法计算上界
void get_up();
//计算下界
void get_down();
//贪心算法
int dfs(int, int, int);
//待处理节点表
struct Node
{
//节点访问标记
bool visited[MAXLENGTH];
//访问次序
int cixu[MAXLENGTH];
//第一个点
int start;
//第一个节点的邻接节点
int start_p;
//最后一个节点
int end;
//最后一个节点的邻接节点
int end_p;
//走过的点数
int k;
//经过路径的距离
int sumv;
//目标函数值
int lb;
//运算符重载
//目标函数值小的先出队列
bool operator <(const Node &p) const{
return p.lb < lb;
}
};
Node tmp;
//int number[20];
int result[MAXLENGTH];
int times=0;
//计算目标函数值
int get_lb(Node);
//建立一个优先队列
priority_queue<Node> pq;
int main(){
cout << "请输入城市个数:";
cin >> n;
cout << "请输入"<<n<<"*"<<n<<"的城市距离代价矩阵:" << endl;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
cin >> value[i][j];
}
}
Modify();
//输出最终结果
cout << "最短路径长度为" << solve() << endl;
for (int i = 1; i <= n; i++)
{
//cout<
cout << result[i];
}
getchar();
getchar();
getchar();
return 0;
}
//距离修正函数
void Modify(){
for (int i = 1; i <= n; i++)
{
value[i][i] = 999;
}
}
//定义解决函数
int solve(){
//1.根据衔接函数计算目标函数的上界、下界
get_up();
cout << "贪心法求得上界为:" << up << endl;
get_down();
cout << "下界为:" << down << endl;
//2.计算根节点的目标函数值,并加入待处理节点队列
Node star;
star.start = 1;
star.end = 1;
star.k = 1;
for (int i = 1; i <= n; i++)
{
star.visited[i] = false;
star.cixu[i] = 1;
}
star.visited[1] = true;
star.cixu[1] = 1;
//number[times++] = 1;
//经过的路径距离
star.sumv = 0;
//下界
star.lb = down;
//问题的解
int ret = 999;
//将起点加入队列
pq.push(star);
cout << "start:lb=" << star.lb << endl;
//3.循环直到某个叶子节点的目标函数值在队列中取得最小值
while (pq.size())
{
//3.1获得具有最小值的节点
tmp = pq.top();
pq.pop();
//3.2对节点的每个孩子节点执行下列操作
//如果已经走了n-1个节点
if (tmp.k==n-1)
{
//找最后一个没走过的节点
int p;
for (int i = 1; i <= n; i++)
{
if (!tmp.visited[i]){
p = i;
//number[times++] = i;
tmp.cixu[tmp.k + 1] = i;
break;
}
}
//计算全部的路径长度和
int ans = tmp.sumv + value[tmp.end][p] + value[p][tmp.start];
//如果当前的路径比所有的目标函数值都小则跳出
if (ans <= tmp.lb){
ret = min(ans, ret);
for (int j = 1; j <= n; j++)
{
result[j] = tmp.cixu[j];
}
}
else
{
//更新上界
up = min(ret, ans);
ret = min(ret, ans);
continue;
}
}
Node next;
for (int i = 1; i <= n; i++)
{
if (!tmp.visited[i])
{
//起点不变
next.start = tmp.start;
//更新路径和
next.sumv = tmp.sumv + value[tmp.end][i];
int tmpstart = tmp.end;
next.end = i;
next.k = tmp.k + 1;
for (int j = 1; j <= n; j++)
{
next.visited[j] = tmp.visited[j];
next.cixu[j] = tmp.cixu[j];
}
next.visited[i] = true;
next.cixu[next.k] = i;
//3.2.1估算每个孩子节点的目标函数值
next.lb = get_lb(next);
//3.2.2判断是否需要丢弃该结点
if (next.lb > up){
continue;//不加入队列
}
pq.push(next);
//number[times++] = i;
cout << tmpstart << "->" << next.end << ":lb=" << next.lb << endl;
}
}
}
return ret;
}
//定义函数上界函数
void get_up(){
dfs_visited[1] = true;
//和实验三相比有了一些改进
up = dfs(1, 1, 0);
}
//定义贪心算法,计算上界
int dfs(int u, int k, int l){
int minlen = 999;
int p;
if (k==n)
{
//最后最后一个节点和开始节点的距离
//minlen = value[u][1];
return l + value[u][1];
}
for (int i = 1; i <= n; i++)
{
if (!dfs_visited[i]&&value[u][i]<minlen)
{
minlen = value[u][i];
p = i;
}
}
//标记当前行中最小节点为访问过
dfs_visited[p] = true;
return dfs(p, k + 1, l + minlen);
}
//定义下界函数
void get_down(){
//取每行最小的两个元素相加再除以2的值作为下界
down = 0;
for (int i = 1; i <= n; i++)
{
//创建一个临时数组,用于存储每一行的数据
int temp[MAXLENGTH];
for (int j = 1; j <= n; j++)
{
temp[j] = value[i][j];
}
//对数组进行排序,参数分别为起始位置,结束位置
sort(temp + 1,temp + n + 1);
//加上最小的两个值
down = down + temp[1] + temp[2];
}
//将down除以2
down /= 2;
}
//计算目标函数值
int get_lb(Node p){
int ret = p.sumv * 2;//路径上的点的距离的二倍
int min1 = 999, min2 = 999;//起点和终点连出来的边
for (int i = 1; i <= n; i++)
{
//cout << p.visited[i] << endl;
if (!p.visited[i] && min1 > value[p.start][i])
{
min1 = value[p.start][i];
}
//cout << min1 << endl;
}
ret += min1;
for (int i = 1; i <= n; i++)
{
if (!p.visited[i] && min2 > value[p.end][i])
{
min2 = value[p.end][i];
}
//cout << min2 << endl;
}
ret += min2;
for (int i = 1; i <= n; i++)
{
if (!p.visited[i])
{
min1 = min2 = 999;
for (int j = 1; j <= n; j++)
{
if (min1 > value[i][j])
min1 = value [i][j];
}
for (int j = 1; j <= n; j++)
{
/*if (min2 > cost[j][i])
min2 = cost[j][i];*/
if (min2>value[i][j] && value[i][j]>min1)
{
min2 = value[i][j];
}
}
ret += min1 + min2;
}
}
return ret / 2;
}