https://huaweicloud.csdn.net/63806872dacf622b8df86ebe.html?spm=1001.2101.3001.6650.6&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Eactivity-6-113065064-blog-121124290.235%5Ev38%5Epc_relevant_sort&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Eactivity-6-113065064-blog-121124290.235%5Ev38%5Epc_relevant_sort&utm_relevant_index=11
1 前言
有关TSP问题的求解方法层出不穷,遗传算法、模拟退火、粒子群算法之类的介绍已经比较多了,但是发现关于自适应大领域搜索算法(Adaptive Large Neighborhood Search, ALNS)的介绍比较少,而且示例代码注释不太详细,刚接触的时候学起来有点费劲,就把学习资料和自己理解的过程简单写一下,造福和我一样的菜鸟吧。
2 什么是自适应大领域搜索
概念理解:干货 | 自适应大邻域搜索(Adaptive Large Neighborhood Search)入门到精通超详细解析-概念篇
优点、步骤和python示例代码:自适应大邻域搜索算法
参考文献及算法应用:
[1]王新. 车辆和无人机联合配送路径问题研究[D].大连海事大学,2020.
[2]李婷玉. 多商户多车程同城物流配送车辆调度问题研究[D].大连理工大学,2018.
[3]张梦颖. 不确定因素下路径规划问题研究[D].中国科学技术大学,2016.
简而言之,自适应大领域搜索的核心思想是:破坏解、修复解、动态调整权重并选择(自适应)。通过设计多组破坏算子和修复算子,扩大解空间搜索范围,对当前解进行改进,表现好的破坏和修复方法相应地得到高分,权重也越高。在每次迭代中,根据以往表现对各个破坏和修复算子进行选择和权重调整,使用高效的组合方法,提高算法寻优能力,从而找到最优解。
2.1 破坏算子(destroy)
破坏解的方法主要有随机移除、最差移除、相似移除等。其中,随机移除删除当前解决方案中的任意节点;最差移除删除当前解决方案中距离较长的路段。
先破坏再修复,这一步得到破坏后的解和移除的节点列表。如,当前解(TSP城市访问顺序)为【1, 4, 2, 3】,随机删除节点4后,得到破坏后的解【1, 2, 3】
2.2 修复算子(repair)
修复解的方法主要有随机插入、贪婪插入等。其中,随机插入将移除的节点逐个插入到破坏后解的任意位置;贪婪插入将移除的节点插到距离成本最小,即插入后总路径最短的位置中。
修复操作的对象是破坏后的解和移除的节点列表,将移除的节点重新插入到破坏后的解中,得到完整的、新的一组解。如,将2.1中的移除节点4,随机插入到破坏后的解【1, 2, 3】中,可得【1, 2, 3, 4】
需要选择至少两组破坏和修复算子加入算法,更多的算子介绍详见上面的参考文献,也可以根据问题特性自行设计其他的破坏或修复算子。
2.3 动态调整权重并选择(自适应过程)
算法在迭代时根据算子权重和轮盘赌的方式选择、调整破坏算子和修复算子。
(1)权重更新
可设定以下4种加分情况:
(1)破坏/修复后得到新的全局最优解,+1.5分
(2)破坏/修复后没有得到全局最优解:
①尚未接受过的但比当前解好,+1.2分
②尚未接受过的且比当前解差:
a)在一定标准下接受劣解,+0.8分
b)不满足接受准则的劣解,+0.6分
阶梯分数自己定,1.5、1.2为示例。搜索过程中,若只接受优解容易陷入局部最优,为了避免这种情况应采用一定准则接受劣解。自适应大领域搜索中通常采用模拟退火算法Metropolis准则,在一定概率下接受劣解。
模拟退火算法的介绍:深度学习 — 模拟退火算法详解(Simulated Annealing, SA)
(2)轮盘赌选择算子
得到新的权重后,算法基于轮盘赌的思想对算子进行选择,使算子被选中的概率与其权重表现成正比。
轮盘赌介绍:Evolutionary Computing: 遗传算法_轮盘赌选择(转载)
3 python代码示例及详解
了解了上面的内容之后,下面这个示例代码就很容易理解了。
优点、步骤和python示例代码:自适应大邻域搜索算法
原来的程序把两个轮盘赌函数放在了前面。为了方便理解,我把函数定义的顺序换了一下,由简到易,先分块注释:
(1)导入库,输入城市两两距离矩阵
import numpy as np
import random as rd
import copy
distmat = np.array([[0,350,290,670,600,500,660,440,720,410,480,970],
[350,0,340,360,280,375,555,490,785,760,700,1100],
[290,340,0,580,410,630,795,680,1030,695,780,1300],
[670,360,580,0,260,380,610,805,870,1100,1000,1100],
[600,280,410,260,0,610,780,735,1030,1000,960,1300],
[500,375,630,380,610,0,160,645,500,950,815,950],
[660,555,795,610,780,160,0,495,345,820,680,830],
[440,490,680,805,735,645,495,0,350,435,300,625],
[720,785,1030,870,1030,500,345,350,0,475,320,485],
[410,760,695,1100,1000,950,820,435,475,0,265,745],
[480,700,780,1000,960,815,680,300,320,265,0,585],
[970,1100,1300,1100,1300,950,830,625,485,745,585,0]])
(2)定义目标函数计算距离 —— disCal( )
已知访问顺序path,求经过的路径长度distance。
def disCal(path):
distance = 0 #若路径为path = 【1,2, 3】,len(p