转 | 禁忌搜索算法(Tabu Search)求解带时间窗的车辆路径规划问题详解(附Java代码)

以下文章来源于数据魔术师 ,作者周航

欲下载本文相关的代码及算例,请关注公众号【程序猿声】,后台回复【TSVRPJAVA】不包括【】即可

前言

大家好呀!

眼看这9102年都快要过去了,小编也是越来越感觉着急了:

为什么感觉自己今年还这么蔡!

所以赶紧趁考试周来临前,码出了这篇禁忌搜索算法解决VRPTW的文章,临时抱佛脚,假装自己今年学了一点东西。

本文附带Java代码详解,是根据过去学长写的C++代码修改而来的:

干货 | 十分钟掌握禁忌搜索算法求解带时间窗的车辆路径问题(附C++代码和详细代码注释)

新的代码加入了原先忘加的藐视准则,将一些冗余代码改为函数调用,同时加入大规模注释,很适合没尝试过VRPTW的同学学习(没错就是我自己)。

算例是用上文的格式,所以建议在仔细阅读本文代码之前先浏览一下上文。

下面就开始今天的分享吧!

VRPTW简介

VRPTW问题可描述为:假设一个配送中心为周围若干个位于不同地理位置、且对货物送达时间有不相同要求的客户点提供配送服务。其中,配送中心用于运行的车辆都是同一型号的(即拥有相同的容量、速度);配送中心对车辆出入的时间有限制。我们的任务是找出使所有车辆行使路径总和最小的路线。

VRPTW的更多详细介绍可以参考之前的推文:

干货|十分钟快速掌握CPLEX求解VRPTW数学模型(附JAVA代码及CPLEX安装流程)

为了保持文章的独立型,同时方便后续讲解,这里给出建模实例(参考文献在文末标注):

所求的所有车辆路线需满足以下要求:

在此基础上求出每辆车辆的总时间最短(由于车辆速度相同,时间最短相当于路程最短)的路线。(允许不使用某些车辆)

Tabu Search简介

禁忌搜索算法(Tabu Search Algorithm,简称TS)起源于对于人类记忆功能的模仿,是一种亚启发式算法(meta-heuristics)。它从一个初始可行解(initial feasible solution)出发,试探一系列的特定搜索方向(移动),选择让特定的目标函数值提升最多的移动。为了避免陷入局部最优解,禁忌搜索对已经历过的搜索过程信息进行记录,从而指导下一步的搜索方向。

禁忌搜索是人工智能的一种体现,是局部搜索的一种扩展。禁忌搜索是在邻域搜索(local search)的基础上,通过设置禁忌表(tabu list)来禁忌一些曾经执行过的操作,并利用藐视准则来解禁一些优秀的解。

有关禁忌搜索算法的具体内容可以参考往期推文:

干货 | 到底是什么算法,能让人们如此绝望?

干货|十分钟快速复习禁忌搜索(c++版)

TS求解VRPTW

对邻域搜索类算法而言,采取的搜索算子和评价函数至关重要。下面详细介绍代码中针对VRPTW的插入算子和评价函数。

插入算子:

评价函数:

算法概述

Java代码详解

欲下载本文相关的代码及算例,请关注公众号【程序猿声】,后台回复【TSVRPJAVA】不包括【】即可

代码主要分为以下几个类:

Main,主函数;
CustomerType,存放客户节点的信息;
RouteType,存放车辆路线信息;
Parameter,存放全局变量;
EvaluateRoute,处理路线方法;
InitAndPrint,初始化与输出对应方法;
TS,禁忌搜索方法。

接下来分别介绍。

Main:程序的入口。
CustomerType:客户类,对图中的每一个客户,分别构建客户类,存放自身编号,所属车辆路线,坐标位置,访问时间窗,服务所需时长、需求。

RouteType:路线类,记录该路线的总承载量,总长度,对时间窗约束的总违反量,以及单条路径上的客户节点序列。

Parameter:参数类,有关VRPTW和TS的变量都存储在这里,在这里修改数据。

EvaluateRoute:check函数是对产生解的检验。

由于插入算子产生的解并不都满足所有约束条件,对局部搜索产生的较优解需要判断是否满足时间窗约束和容量约束后,再决定是否为可行解。

在check局部最优解的过程中,修改惩罚系数Alpha、Beta的值。

UpdateSubT函数更新一条车辆路线中在每一个客户点的时间窗违反量。通过遍历整条路线累加得到结果。

Calculate函数计算目标函数值,惩罚部分累加后乘以惩罚系数。

InitAndPrint:根据计算距离。

从文件中读取算例(在此修改算例,记得同时修改Parameter类中的参数),并对当前解routes[ ]的每条路线进行初始化,起终点都为配送中心。

记录客户间距离,存储在Graph数组中。

Construction构造初始解。根据前文伪代码构造初始解,每次随机选择节点(类似打乱有序数列)。

针对该节点找到符合容量约束,同时时间窗开启时间符合要求的位置,插入该节点。记得在插入节点时同时更新该节点所属的路径。对时间窗违反量进行初始化。

最后加入一个CheckAns函数,检验一下输出解是否满足时间窗约束,计算的距离是否正确,有备无患~

TS:先是两个辅助函数,addnode和removenode,他们是插入算子的执行部分。

现在万事俱备,只欠东风,只需要按照禁忌搜索的套路,将所有工具整合在一起,搭建出代码框架就ok啦。

由于我们采用routes[ ]数组存储当前解,因此在进行插入操作之前要存储部分数据,在计算完目标函数之后要进行复原操作。

在更新禁忌表时,对禁忌步长的计算公式可以灵活改变。

记得对局部最优解进行判断,再选取为可行的全局最优解。

算例展示

我们采用标准solomon测试数据c101.txt进行测试。(算例可在留言区下载获取)

VehicleNumber = 25;

Capacity=200;

分别测试节点数25,50,100的情况。精确解分别为:

CustomerNumber=25:

CustomerNumber=50:

CustomerNumber=100:

可见我们的代码精确度还是很可以滴~~

当然不排除运气不好,得出很差解的情况。不相信自己的人品可以手动调整迭代次数IterMax。

本期的内容到这里就差不多结束了!开心!

在这里提醒大家一下,在针对启发式算法的学习过程中,编写代码的能力是很重要的。VRPTW是一个很好的载体,建议有时间的读者尽量将学到的算法知识运用到实践中去。小编会和你们一起学习进步的!

下次再见ヾ( ̄▽ ̄)ByeBye

参考文献:

Cordeau, J. F. , Laporte, G. , & Mercier, A. . (2001). A unified tabu search heuristic for vehicle routing problems with time windows. Journal of the Operational Research Society**, 52(8), 928-936.

代码参考:

干货 | 十分钟掌握禁忌搜索算法求解带时间窗的车辆路径问题(附C++代码和详细代码注释)

posted @ 2020-04-10 12:57  短短的路走走停停  阅读(2442)  评论(1编辑  收藏  举报