基于模拟退火算法(SAA)的TSP(旅行商)问题求解

编写参考:https://www.cnblogs.com/ranjiewen/p/6084052.html

【问题描述】

有n个城市坐标,问你从任意一个城市出发经过所有城市后再回到原城市且每个城市仅旅行一次的最短距离是多少

【求解思路】

这是一个经典的完全NP问题,对于此类问题,目前并没有任何高效算法能求得此问题的最优解,而只能通过模拟退火或者是遗传算法求得近似解

本文采用模拟退火求解该问题

【代码实现】

#pragma GCC optimize(2)
#include   <time.h>
#include   <cstdio>
#include   <cstring>
#include   <iostream>
#include   <algorithm>
#define  N          1010  //最大城市数
#define  eps        1e-8  //结束温度
#define  rate       0.98  //温度下降速率
#define  length     1000  //迭代次数
#define  INF  0x3f3f3f3f  //近似无穷大
using namespace std;
int n;  //城市数目
double T = 100000 / 1.0;  //初始温度
struct path
{
	int ans[N];  //保存路径
	double len;  //路径总长
	path()
	{
		ans[0] = 0;
		for (int i = 1; i < N; ++i)
		{
			ans[i] = 0;
		}
		len = INF;
	}
	void operator = (const path& b)
	{
		for (int i = 0; i < n; ++i)
		{
			this->ans[i] = b.ans[i];
		}
		this->len = b.len;
	}
}bestpath, ans;
struct point
{
	double x, y;
	point()
	{
		x = y = 0;
	}
};
point p[N];
double d[N][N];  //存放城市之间的路程
void swap(int& x, int& y)
{
    if(x == y) return;
	x = x ^ y;
	y = x ^ y;
	x = x ^ y;
}
double cal(point a, point b)  //根据坐标计算路径
{
	return sqrt((1.0 * (a.x - b.x) * (a.x - b.x) + 1.0 * (a.y - b.y) * (a.y - b.y)));
}
void getdis()
{
	for (int i = 0; i < n - 1; ++i)
	{
		for (int j = i + 1; j < n; ++j)
		{
			d[i][j] = d[j][i] = cal(p[i], p[j]);
		}
	}
}
double Rand()  //产生小于1的随机数
{
	return (((double)rand()) / (RAND_MAX + 1.0));
}
path getnext(path pp)  //随机交换路径中的两个点寻找最优解
{
	path temp = pp;
	int x = 0, y = 0;
	while (x == y)
	{
		x = (int)(n * Rand());
		y = (int)(n * Rand());
	}
	swap(temp.ans[x], temp.ans[y]);
	temp.len = 0;
	for (int i = 0; i < n; ++i)
	{
		if (i != n - 1)
			temp.len += d[temp.ans[i]][temp.ans[i + 1]];
	}
	temp.len += d[temp.ans[n - 1]][temp.ans[0]];
	return temp;
}
void print()  //打印最终最优路径
{
	printf("The approximate solution is:\n");
	for (int i = 0; i < n; ++i)
	{
		printf("%d->", ans.ans[i]);
	}
	printf("%d\n", ans.ans[0]);
	printf("The length of the solution is: %.6f\n", ans.len);
}
void SAA()
{
	getdis();  //首先预处理处所有城市间的路径
	for (int i = 0; i < n; ++i)  //答案初始化
	{
		bestpath.ans[i] = i;
		if (i != n - 1)
		{
			bestpath.len += d[i][i + 1];
		}
	}
	bestpath.len += d[n - 1][0];
	path newpath = bestpath;
	while (T > eps)  //从初始温度开始进行降温
	{
		for (int i = 1; i <= length; ++i)
		{
			newpath = getnext(bestpath);  //产生新路径
			double delta = bestpath.len - newpath.len;
			if (delta > 0.0)  //如果当前新求解的路径小于最优路径,更新最优路径
			{
				bestpath = newpath;
				if (ans.len > bestpath.len) ans = bestpath;  //ans由于保存所有产生路径中最短的那条
			}
			else if (exp(delta / T) > Rand())  //否则以一定概率接受差解
			{
				bestpath = newpath;
			}
		}
		T *= rate;  //降温
	}
	print();
}
int main()
{
	srand((unsigned)time(NULL));  //使用真随机数
	scanf_s("%d", &n);
	if (n > N)
	{
		printf_s("so large size!\n");
		exit(0);
	}
	for (int i = 0; i < n; ++i)
	{
		scanf_s("%lf%lf", &p[i].x, &p[i].y);
	}
	SAA();
	return 0;
}

【演示数据】

样例解释

第一行输入总城市数n,随后的n行包含编号0 - (n - 1)所有城市的坐标

样例1:

6

20 80

16 84

23 66

62 90

11 9

35 28

样例2:

27

41 94

37 84

53 67

25 62

7 64

2 99

68 58

71 44

54 62

83 69

64 60

18 54

22 60

83 46

91 38

25 38

24 42

58 69

71 71

74 78

87 76

18 40

13 40

82 7

62 32

58 35

45 21

【演示文件】

样例1

样例1

样例2

样例2

【基于枚举算法的算法正确性验证】

#pragma GCC optimize(2)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define INF 0x7fffffff
using namespace std;
const int maxn = 1010;
struct point
{
	double x, y;
}a[maxn];
int b[maxn];  //用于记录所有路径的全排列
double dis(point a, point b)
{
	return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
int main()
{
	int n;
	scanf_s("%d", &n);
	for (int i = 0; i < n; i++)
	{
		scanf_s("%lf%lf", &a[i].x, &a[i].y);
	}
	for (int i = 0; i < n; i++)
	{
		b[i] = i;
	}
	double ans = INF / 1.0;
	do
	{
		double t = 0.0;
		for (int i = 0; i < n; i++)
		{
			if (i != n - 1)
			{
				t += dis(a[b[i]], a[b[i + 1]]);
				if (t >= ans)   //剪枝,距离大于当前解跳出
					break;
			}
		}
		t += dis(a[b[n - 1]], a[b[0]]);
		ans = min(ans, t);
	} while (next_permutation(b, b + n));  //全排列
	printf_s("The accurately anser is: %lf\n", ans);
	return 0;
}

演示结果

样例1

样例1

样例2

(电脑跑不动)

可以看到,SAA所产生的近似解与最优解相差不大

【结果分析】

样例1中,SAA与搜索出来的答案几近相同,但时间上比搜索略慢,说明在点数较少时,初始温度过高可能会导致运行的时间加长,并且点数较少时答案较为可靠。

而在样例2中,搜索的运行时间远远高于模拟退火(搜索复杂度O(n!)),时间上搜索无法接受,而SAA却可在极短时间内产生近似最优解,再次证明了SAA算法的优点所在。

【后记】

目前本人尚不清楚此问题的初始温度及降温速率如何设置比较合理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值