我们已经讲解了使用GA求解0-1背包问题(遗传算法求解0-1背包问题(附matlab源代码)),车间调度问题(遗传算法求解车间调度问题(附MATLAB代码)),以及对BP神经网络的参数优化问题(机器学习 | 基于遗传算法的BP神经网络优化算法(附MATLAB代码))。今天小编来为大家讲解使用遗传算法(简称GA)求解带时间窗的车辆路径问题(VRPTW)。
01 | 问题描述
VRPTW是指一定数量的客户,各自有不同数量的货物需求,配送中心向客户提供货物,由一个车队负责分送货物,组织适当的行车路线,目标是使得客户的需求得到满足,并能在一定的约束下,达到诸如路程最短、成本最小等目的。
02 | 算法设计
(1)编码
在使用GA求解TSP问题时,我们可以简单地采用整数编码的形式对染色体进行编码,比如城市个数为5,那么一个可行的染色体表达为12345。
在使用GA求解VRPTW问题时,我们依然想采用这种简洁的编码方式对染色体进行编码,不过需要稍微做一下改进,比如说需要服务的顾客数目为5,而最多允许使用3辆车来服务这些顾客,那么一种可行的染色体表达为1263475,那么这个1263475究竟代表什么意思呢?
其中1263475中的6和7代表配送中心,6和7将顾客12345划分为3段,即划分为3条路径。
(0代表配送中心)
第1条路径为0-1-2-0
第2条路径为0-3-4-0
第3条路径为0-5-0
如果染色体表达为1236745,那么这个染色体表示2条配送路径。
(0代表配送中心)
第1条路径为0-1-2-3-0
第2条路径为0-4-5-0
可以看出当顾客数目为N且最大车辆使用数目为K时,染色体长度为N+K-1,染色体表达形式为(1,2,3,……,N,N+1,N+2,……,N+K-1)
(2)适应度函数
当然采用上述编码方式不能保证解码的各条配送路径都满足载重量约束和时间窗约束,所以为了能够简单解决违反约束这一问题,我们使用惩罚函数的办法来进行求解。
具体计算公式如下:
c(s)表示车辆总行驶距离,q(s)表示各条路径违反的容量约束之和,w(s)表示所有顾客违反的时间窗约束之和。因为违反容量约束相对来说不太容易,所以将α设为10。而较容易违反时间窗约束,所以将β设为100。
ai表示车辆到达顾客i的时间,li表示顾客i的右时间窗。
因为目标函数越小越好,而在选择操作时需要将适应度值大的个体选择出来,所以这里我们将适应度函数设为惩罚函数的倒数,即为1/f(s)。
(3)种群初始化
在初始化种群之前,我们先构造VRPTW问题的初始解。
那么如何采用启发式方法构造VRPTW问题的初始解?
这里需要注意的一点是,我们所构造的初始解并不一定能够满足载重量约束和时间窗约束,但是我们所构造的初始解能在一定程度上降低GA的搜索难度。
假设顾客数目为n。
STEP1:从所有顾客中随机选择一个顾客j∈{1,……,n}
STEP2:初始化车辆使用数目k←1
STEP3:生成一个遍历顾客的序列Seq←[j,j+1,……,n,1,……,j-1]
STEP4:遍历序号为i,一直遍历到n即生成初始解。按遍历照序Seq遍历顾客Seq(i),将顾客Seq(i)添加到第k条路径中,然后分2种情况:
1、如果第k条路径满足载重量约束,那么这里又分为3种情况:
1.1、如果当前路径为空,直接将顾客Seq(i)添加到路径中;
1.2、如果当前路径只有一个顾客,再添加新顾客Seq(i)时,需要根据左时间窗大小进行添加;
1.3、如果当前路径访问的顾客数目lr大于1,则需要遍历这lr-1对连续的顾客的中间插入位置,然后确认顾客Seq(i)的左时间窗能否在中间插入位置前后的顾客的左时间窗之间。如果存在这样的中间插入位置,则将顾客Seq(i)插入到该位置。如果不存在这样的中间插入位置,则将顾客seq(i)插到当前路径末尾。
2、如果第k条路径不满足载重量约束,则先储存第k辆车在访问顾客Seq(i)之前所访问的顾客,然后更新k←k+1在构造完初始解之后,如何初始化种群?
这个初始解就是一个完整的配送方案,例如有5个顾客,最多能使用3辆车,那么一个配送方案就可表示为
第1条路径为0-1-2-0
第2条路径为0-3-4-0
第3条路径为0-5-0
然后我们再将这个配送方案中的各条路径进行合并,即为1203405,其中0代表配送中心,所以用6和7将1203405中的0依次替换掉,结果为1263475,即为一个个体。所以按照上述方法,将构造完的初始解转换为个体。
最后再将这个种群的所有个体全部赋值为初始解转换的个体,至此种群初始化大功告成。
(4)选择操作
选择操作我们就采用轮盘赌选择,按照适应度值的大小选择若干个适应度值大的个体进行后续的交叉、变异以及局部搜索操作。
(5)OX交叉操作
交叉操作我们使用OX交叉的方式,什么时OX交叉呢?下面小编用实例进行演示。比如说有两个父代个体为
父代1:1 2 3 4 5 6 7 8
父代2:8 7 6 5 4 3 2 1
这时随机选择两个交叉位置a和b,比如说a=3,b=6,那么交叉的片段为:
父代1:1 2 | 3 4 5 6 | 7 8
父代2:8 7 | 6 5 4 3 | 2 1
然后将父代2的交叉片段移动到父代1的前面,将父代1的交叉片段移动到父代2的前面,则这两个父代个体变为:
父代1:6 5 4 3 1 2 3 4 5 6 7 8
父代2:3 4 5 6 8 7 6 5 4 3 2 1
然后从前到后把第2个重复的基因位删除掉,我们先把两个父代个体中重复的基因位标记出来:
父代1:6 5 4 3 1 2 3 4 5 6 7 8
父代2:3 4 5 6 8 7 6 5 4 3 2 1
然后把第2个重复的基因位删除,形成两个子代个体。
子代1:6 5 4 3 1 2 7 8
子代2:3 4 5 6 8 7 2 1
(6)变异操作
变异操作比较简洁,就是先生成两个位置,然后将这两个位置上的基因进行交换。比如说,有如下个体:
父代:1 2 3 4 5 6 7 8
这时随机选择两个变异位置a和b,比如说a=3,b=6,那么交换后的个体为:
子代:1 2 6 4 5 3 7 8
(7)局部搜索操作
之前没打算加局部搜索操作,不过仅凭OX交叉操作和变异操作,求得的结果实在是辣眼睛,所以我们不得不加了局部搜索操作,果然加完这个局部搜索操作以后,虽然求解时间增加了不少,但是求解质量那是相当的好。
这里的局部搜索我们使用了大规模邻域搜索算法(LNS)通俗讲解这篇推文中的破坏和修复的思想。简单来说我们使用破坏算子从当前解中移除若干个顾客,然后再使用修复算子将被移除的顾客重新插回到破坏的解中。
当然了,我们不是随随便便地说移除哪个顾客就移除哪个顾客,而是根据相似性计算公式移除若干个相似的顾客。
同样,我们不是将移除的顾客随随便便地插入到某条路径的某个插入位置,而是在满足载重量约束和时间窗约束的前提下,尽可能将移除的顾客插回到使车辆行驶总距离增加最少的插入位置。
关于破坏算子和修复算子的具体内容可以通过大规模邻域搜索算法(LNS)通俗讲解这篇推文进行学习。
03 | MATLAB代码(后台回复“GA VRPTW”提取代码)
点击左下方阅读原文提取代码(提取码:q7fh)
04 | 参考
1、郎茂祥. 基于遗传算法的物流配送路径优化问题研究[J]. 中国公路学报, 2002, 15(3):76-79.
2、Cordeau J F , Laporte G , Mercier A . A unified tabu search heuristic for vehicle routing problems with time windows[J]. Journal of the Operational Research Society, 2001, 52(8):928-936.
其实,在这里可以给各位小伙伴留两个思考题。
1、如果各位小伙伴不使用小编在推文中的编码方式,各位小伙伴还可以采用哪种编码方式呢?
2、如果各位小伙伴不使用小编在推文中的局部搜索方法,各位小伙伴还可以采用哪种局部搜索方法呢?
知乎:随心390
长按识别关注我们