旅行推销员问题(Travelling Salesman Problem, TSP)是组合数学中一个古老而困难的问题,至今尚未彻底解决,是组合优化中的一个NP-完备问题类。这个问题描述的是:有一货物推销员要去若干个城市推销货物,从城市1出发,经其余各城市至少一次,各城市间的距离已知,然后回到城市1,问图和选择行走路线,才能使得总行程最短。显而易见,城市的数量越多,该问题就越困难。针对这个问题并没有通用的解决办法,尽管目前已经有一些指数级的算法(具体见文末)可精确的求解TSP,但对于大规模的问题会出现组合爆炸的现象,故而人们退而求其次,转向寻找近似算法或启发式算法,并取得了相当的进展。本文将重点用模拟退火地方法解决旅行推销员问题。

%=====================================================================%
模拟退火方法(Simulated Annealing Algorithm)是一种源于五十年代、基于Monte Carlo迭代求解思想的随机搜索算法,其出发点是将组合优化问题与统计力学的热平衡做类比,把优化的目标函数作为能量函数,模拟物理学中固体物质的退火处理,先加温使之具有足够高的能量,再使系统处于等温过程,是自由能达到最小值,系统处于平衡状态,最后再逐渐降温,其内部能量也相应下降,在热平衡条件下,物体内部处于不同状态的概率服从Boltzman分布(描述一定温度下微观粒子运动速度的概率分布)。若退火步骤恰当,则最终会行程最低能量的基态。这种算法思想在求解优化问题时,不但接受对目标函数有改进的状态,还能以某种概率接受使目标函数恶化的状态,从而可使之避免过早收敛到某个局部极值点,也正是这种概率性的扰动能够使之跳出局部极值点,故而得到的解常常很好。
模拟退火法所得解的好坏与初始状态、温度函数等都有一定的联系,降温较快的效果不一定很好,效果好的,其降温过程又极其缓慢。但由于该方法适用范围广,并可认为控制迭代次数反复求解,因此具有很强的实用性。
%========================================================================%
下面我们开始讨论如何用模拟退火方法求解TSP:
记住两条重要的规则:
1.每个城市有且仅能访问一次
2.必须返回起始城市
数学模型:

模拟退火算法的进行过程:
1、选择初始状态H(初始解)、初始温度、降温次数;
2、生成H的领域状态H’,并计算Z(H’)-Z(H);
3、按接收概率置换H;
4、重复步骤2直到停机条件满足。
创建模拟退火算法:
①导入一些常见的包
import numpy as np
import random
import matplotlib.pyplot as plt
import os
import shutil
import imageio
其中,os即operation system(操作系统),封装了常见的文件和目录操作;shutil为高级的文件、文件夹、压缩包的处理模块;imageio用于gif的绘制。
②为了使数据更具有一般性,在具体算法之前,我们首先随机生成各城市坐标(假设共50个城市),并定义需要的数据。
i>随机生成各点并定义各城市间的距离
def create_data(N, xu=25, yu=25, xd=-25, yd=-25):
fx = lambda: random.random() * (xu - xd) + xd
fy = lambda: random.random() * (yu - yd) + yd
calDistance = lambda x, y: np.sqrt((x[0] - y[0]) ** 2 + (x[1] - y[1]) ** 2)
points = [(0, 0)] * N
for i in range(N):
points[i] = (fx(), fy())
Mat = np.zeros((N, N))
for i in range(N):
for j in range(i + 1, N):
dv = calDistance(points[i], points[j])
Mat[i][j], Mat[j][i] = dv, dv
return points, Mat
我们可以得到随机生成的各城市的坐标以及其距离矩阵(由于随机性每一次生成的城市坐标均不一样)


ii>定义推销员所走过的总距离,即为每个城市之间的距离总和
def calpathValue(path):
global Mat
temp = Mat[0][path[0]]
for i in range(len(path) - 1):
temp += Mat[path[i]][path[i + 1]]
temp += Mat[path[-1]][0]
return temp
③创建模拟退火算法
i>创建目录并设定城市数目,设置初始温度、更新温度、每个温度下的迭代次数
if __name__ == '__main__':
# N, Mat = read_data()
TIMESIT = 0
PNGFILE = './png/'
PNGLIST = []
if not os.path.exists(PNGFILE): #判断PNGFILE是否存在
os.mkdir(PNGFILE) #创建目录
else:
shutil.rmtree(PNGFILE) #递归的去删除文件
os.mkdir(PNGFILE)
N = 50
points, Mat = create_data(N)
T = 500 # 起始温度
alpha = 0.995 # T_{k+1} = alpha * T_k方式更新温度
limitedT = 1. # 最小值的T
iterTime = 1000 # 每个温度下迭代的次数
ii>遍历H的领域状态H’,计算路程长度并设置停机条件
while T > limitedT:
print(T)
for i in range(iterTime):
tempPath = path.copy()
tx = random.randint(0, N - 2)
ty = random.randint(0, N - 2)
if tx != ty:
tempPath[tx], tempPath[ty] = tempPath[ty], tempPath[tx]
tempValue = calpathValue(tempPath)
if tempValue <= value:
path = tempPath.copy()
value = tempValue.copy()
else:
p = np.exp((value - tempValue) / (K * T))
if random.random() < p:
path = tempPath.copy()
value = tempValue.copy()
if value < global_Best:
global_Best = value
draw(path, value)
T *= alpha
④输出结果并做动态图
path, value = initial()
tempPath, tempValue = [], 0
global_Best = value # 画图
print(value)
print(0, end='-->')
for i in path:
print(i, end='-->')
generated_images = []
for png_path in PNGLIST:
generated_images.append(imageio.imread(png_path))
generated_images = generated_images + [generated_images[-1]] * 5
imageio.mimsave('tsp.gif', generated_images, 'GIF', duration=0.5)
结果输出:

共保存58张静态图;
将这些静态图绘制成gif,则可得到(插入tsp.gif)

且我们得到的结果为:
结语:
运行过程中深刻的感受到收敛速度慢执行时间较长,且对参数的依赖性较大。但整个SAA创建过程以及输出结果都较有趣。tsp本身作为一个较为经典的NP问题,目前求解的方法也有很多:精确算法如线性规划方法、动态规划方法、分支定界方法;近似方法有插入法、最近邻算法、生成树法;近年来也有一些新的尝试如遗传算法、蚁群算法等,不同的方法都有不同的优缺点。感兴趣的同学可以做进一部分了解。
参考网站
github.com/DiegoVicen/som-tsp
blog.csdn.net/a19990412/article/details/84978612

