旅行商问题描述
旅行商问题(Travelling Salesman Problem,TSP)是:一个商品推销员要去若干个城市推销商品,该推销员从一个城市出发,需要经过所有城市后,回到出发地。应如何选择行进路线,以使总的行程最短?
旅行商问题
旅行商问题的可行解是所有顶点的全排列,随着顶点数的增加,旅行商问题会产生组合爆炸,它是一个NP完全问题,在运筹学和理论计算机科学中非常重要,在交通运输、电路板线路设计以及物流配送等领域有着广泛的应用。最早的旅行商问题的数学规划是由Dantzig(1959年)等人提出的,并且他们在最优化领域中进行了深入研究。许多优化方法都用它作为一个测试基准。尽管问题在计算上很困难,但已经有了大量的启发式算法和精确算法来求解数量上万的实例,并且能将误差控制在1%以内。
早期的研究者使用精确算法求解旅行商问题,常用的算法包括分支定界法、线性规划法、动态规划法等。但是,随着问题规模的增大,精确算法将变得无能为力,因此,在后来的研究中,国内外学者重点使用近似算法或启发式算法,主要有遗传算法、模拟退火算法、蚁群算法、禁忌搜索算法、贪婪算法和神经网络等。
基于模拟退火算法的旅行商问题求解
我们在前面的例子中曾经提过贪心算法。如果把寻找最优值看作下山找到最低处的过程,贪心算法就是朝着海拔下降的方向行进,这有可能陷入一个并不是最低点的山谷(局部最优值),如下图所示。
算法搜索过程
模拟退火算法允许在搜索过程中,以一定的概率进入海拔更高的地方,这样反而有更多的可能性找到真正的低点。模拟退火算法来源于固体退火原理,将固体加到高温,然后徐徐冷却,加温时固体内部粒子随温度的升高变为无序状,内能增大,而冷却时粒子渐趋有序,在每个温度都达到平衡态,最后在常温时达到基态,内能减为最小。根据Metropolis准则,粒子在温度为T时趋于平衡的概率为e(-ΔE/(kT)),其中E为温度为T时的内能,ΔE为其改变量,k为玻尔兹曼(Boltzmann)常数。
如何将上述过程转成一个可以利用的算法?可以看到,在退火过程中,随着内能的减少,当量最少时,粒子趋于平衡,达到某个稳态。如果这个内能对应需要优化的目标函数值,且是求最小值,则当两个解对应的目标函数值的差很小时,可以说进入了某个谷底,为了避免进入局部的谷底,新解的接受与否并不仅取决于目标函数值的差,而是以一定的概率来确定。模拟退火算法的流程如下。
① 初始化:初始温度为T(充分大),温度下限为Tmin(充分小),初始解状态为S(是算法迭代的起点),每个T值的迭代次数都为L。
② 对k=1,…,L做第③至第⑥步。
③ 产生新解S′。
④ 计算增量ΔE=C(S′)-C(S),其中C(S)为评价函数。
⑤ 根据Metropolis准则确定是否接受新解:若ΔE<0则接受S′作为新的当前解,否则以概率exp(-ΔE/T)接受S′作为新的当前解。
⑥ 如果满足终止条件(例如连续若干个新解都没有被接受),则输出当前解作为最优解,结束程序。
⑦ T逐渐减小,且T>Tmin,然后转第②步。当T≤Tmin时,结束程序。
下面我们利用模拟退火算法对TSP问题进行求解,本程序参考https://ericphanson.com/blog/2016/the-traveling-salesman-and-10-lines-of-python/。
--------------------part 1导入相应的库--------------------
import random,numpy,math,copy,matplotlib.pyplot as plt
--------------------part 2 生成初始数据--------------------
#随机生成15个城市
cities=[random.sample(range(100),2)for x in range(15)];
#生成一个旅行路径tour,即城市号的组合
tour=random.sample(range(15),15);
------------------part 3调用模拟退火算法------------------
#生成参数temperature,取值范围为101~105,即[1,100 000],一共有100 000个数(www.xing528.com)
for temperature in numpy.logspace(0,5,num=100000)[::-1]:
#在15个数中任意选择两个位置
[i,j]=sorted(random.sample(range(15),2));
#对原有路径tour中的i,j位置(计数从0开始)的城市进行调换,形成新的路径
newTour=tour[:i]+tour[j:j+1]+tour[i+1:j]+tour[i:i+1]+tour[j+1:];
#计算旧路径中会改变部分的路径长度,即tour中i-1到i+1,j-1到j+1之间的距离
oldDistances=sum([math.sqrt(sum([(cities[tour[(k+1)%15]][d]-cities[tour[k % 15]][d])**2 for d in[0,1]]))for k in[j,j-1,i,i-1]])
#计算新路径中改变部分的路径长度
newDistances=sum([math.sqrt(sum([(cities[newTour[(k+1)% 15]][d]-cities[newTour[k % 15]][d])**2 for d in[0,1]]))
for k in[j,j-1,i,i-1]])
#计算模拟退火算法中的能量变化(此处为路径长度的变化),当大于某个随机概率时,接受新的路径,新路径长度越短,被接受的概率就越大
if math.exp((oldDistances-newDistances)/temperature)>random.random():
tour=copy.copy(newTour);
-----------------------part 4画图-----------------------
plt.plot([cities[tour[i % 15]][0]for i in range(16)],[cities[tour[i % 15]][1]for i in range(16)],'xb-');
plt.show()
下面对以上程序中的重要部分进行解读。
假定有15个城市,这里的目标是列出一个“城市”列表,每个城市都包含两个坐标。
用语句“cities=[random.sample(range(100),2)for x in range(15)]”生成15组城市的坐标,例如[[76,70],[7,94],[33,17],[34,45],[34,13],[62,2],[52,62],[19,58],[60,81],[34,30],[60,84],[95,65],[58,74],[34,15],[98,70]]。
再给这些城市进行编码,利用语句“tour=random.sample(range(15),15)”生成一个列表变量tour,包含15个城市的编号,例如[11,5,4,13,2,9,3,7,1,10,8,12,6,0,14]表示从城市11到城市5,以此类推。
如果要生成新的旅行路径newTour,这里采用在原有路径tour上进行城市置换。假如此时i=2,j=9,则新的路线“newTour=tour[:i]+tour[j:j+1]+tour[i+1:j]+tour[i:i+1]+tour[j+1:]”实际上是将旧路线中的第3和第10个城市进行了互换,如下图所示。
第3和第10个城市互换
因此,在计算路径差时,只需要计算上图圆圈处的路径差,其他部分都是一样的。这就是oldDistances和newDistances计算公式中所呈现的部分。
这样就可以得到优化后的路径,如下图所示。
优化路径结果
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。