【分支限界法】求解TSP问题

问题描述

TSP问题是指旅行家要旅行n个城市,要求各个城市经历且仅经历一次然后回到出发城市,并要求所走的路程最短。
【分支限界法】求解TSP问题_第1张图片

求解思路

  采用贪心法求得近似解为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。
【分支限界法】求解TSP问题_第2张图片
在这里插入图片描述
【分支限界法】求解TSP问题_第3张图片
  应用分支限界法求解图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问题_第4张图片【分支限界法】求解TSP问题_第5张图片

代码实现

/*分支限界法求解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;
}

运行结果

【分支限界法】求解TSP问题_第6张图片

你可能感兴趣的:(算法设计与分析,C语言,C++程序设计)