禁忌搜索(Tabu Search)原理梳理和应用细节-附求解VRPTW问题C++代码

简介: 禁忌搜索(Tabu Search)原理梳理和应用细节-附求解VRPTW问题C++代码

1、禁忌搜索(TS)的相关概念


禁忌搜索的基本原理是在局部搜索(Local Search)的基础上添加“记忆”机制,使搜索方向不能返回到之前的位置,实现这种“记忆”机制的方法为构建“禁忌表”。



1.1 搜索空间(search space)


TS的搜索空间指的是在算法搜索解的过程之中,所有可能被搜寻到的解所构成的集合。例如,在VRPTW问题中,搜索空间中的每一个点表示满足所有问题约束的一组车辆路径集合。值得注意的是:将搜索空间限制在可行解范围之内并不是一种好的策略,允许搜索到不可行解在多数情况下式非常有价值的,有时候这也是必要的(e.g 当初始解构建十分困难的时候)。




1.2 邻域结构(neighborhood structure)

邻域是全部搜索空间(search space)的一个子集。定义邻域为: N ( S ) N(S) N(S)

N ( S ) = { S o l u t i o n s   o b t a i n e d   b y   a p p l y i n g   a   s i n g l e   l o c a l   t r a n s f o r m a t i o n   t o   S } N(S)=\{Solutions \ obtained \ by \ applying \ a \ single \ local \ transformation \ to \ S\} N(S)={Solutions obtained by applying a single local transformation to S}

即对于搜索空间 中的某一个解,执行一次局部转变操作,这个局部转变操作的集合就叫做邻域。


以VRPTW问题为例,下面介绍两种邻域结构:


1 )  简单的VRPTW邻域结构

在当前解 s s s中随机选择一条车辆路径 r n r_n rn中的任意一个客户点 i i i进行移除,之后将移除的客户点 i i i插入到车辆路径 r n 或者 s中的的任意其他一条路径 r m中。


这种邻域结构的关键之处需要考虑怎样选择客户点 i的插入位置:


(1)可以随机选择一条路径中的随机一个位置进行插入;


(2)使用贪婪思想,计算所有插入可能,选择变动成本最小的插入位置进行插入;


(3)使用贪婪+随机的思想,选择某几条路径(2条)进行搜索,选择其中变动成本最小的位置进行插入。


2 )  复杂的VRPTW邻域结构

λ \qquad \lambda λ- i n t e r c h a n g e interchange interchange 方法:允许同时移动不同路径的多个客户点,也允许不同路径之间客户点的相互交换。


e j c t i o n    c h a i n s \qquad ejction\; chains ejctionchains 方法:包含一系列协同的客户点移动操作,如3- e j c t i o n    c h a i n s ejction\; chains ejctionchains 方法包含的动作为:将客户点 v 1 v_1 v1从路径 r 1 r_1 r1移动到路径 r 2 r_2 r2,将客户点 v 2 v_2 v2从路径 r 2 r_2 r2移动到路径 r 3 r_3 r3,将客户点 v 3 v_3 v3从路径 r 3 r_3 r3移动到路径 r 4 r_4 r4。


3 )其他复杂的VRPTW邻域结构


允许路径之间多个客户点进行同时交换操作。


1.3 禁忌表(tabu)


禁忌表是TS区别于传统LS的显著特征,禁忌表的建立是为了阻止算法向着已经探索过的方向进行寻优。禁忌表的存在使得算法能够有效从之前探索果的寻优空间中跳出来,从而增加寻优空间的广度,达到更好的寻优效果。


禁忌表中的禁忌移动采用一种短期记忆的机制,通常采用固定数量的记忆数量进行储存。最常用的禁忌表为:记录当前解之前几步的邻域动作,禁止在当前邻域动作中选择已经存在于禁忌表中的邻域动作。


以VRPTW问题为例:假设现在的邻域动作是将客户点 v 1从路径 r 1移动到路径 r 2,那么禁忌表中的禁忌邻域动作可以是禁止将客户点 v1从路径 r 2 移动到路径 r 1,记为: ( v 1 , r 2 , r 1 ) ,但这种禁忌约束不强,因为一旦客户点 v1又从路径路径 r 2 r_2 r2移动到路径 r 3 ,那么客户点就可能从路径 r3重新移回路径 r 1;较强的禁忌表形式为:禁止客户点 v 1 移动回路径 r 1,记为:(v1,r1),即忽略当前解的影响;更强的禁忌表形式为:要求客户点 v 1在接下来的几步之中禁止向任何路径进行移动,即: ( v 1 )。



1.4 解禁标准(Aspiration criteria)


为了防止禁忌表中的禁忌动作约束过强导致组织了积极搜索方向或者使得算法停滞,使用解禁标准来删除禁忌表中的某些禁忌动作。最简单且常用的解禁标准为:若某一个存在于禁忌表中的邻域动作生成了比当前最优解更优的解时,则将这个邻域动作从禁忌表中进行删除,因为这个新的解明显之前没有被访问过,所以不可能造成重复搜索的问题。



2、禁忌搜索(TS)的算法框架


定义 S S S表示当前解; S ∗ S^* S∗表示最优解; f ∗ f^* f∗表示最优解的目标值; N ( S ) N(S) N(S)表示当前解的邻域; N ˉ ( S ) \bar{N}(S) Nˉ(S)当前解的可行邻域,即 N ( S ) N(S) N(S)去除禁忌表中的邻域动作。


TS的整体算法框架如下表2.1所示。


9a970bf2476f44b08e27dd2f92f882c3.png


2.1 终止准则


终止准则可以参考以下几种:


1) 固定计算次数或者CPU计算时间


2) 连续一定次数没有对最优解进行优化


3) 目标函数值达到一定的阈值标准




3、禁忌搜索(TS)的分散搜索机制(Diversification)


由于TS的本质仍是LS,所以TS本身具有容易陷入局部最优的算法缺陷。为了克服或者弥补这一个缺陷,提出分散搜索的算法机制。分散搜索机制通常基于“长记忆”来完成,例如在频率记忆(frequency memory)中,算法执行过程中会记录下不同的解成分在当前解中出现的迭代次数之和,并应用这个信息来进一步扩大搜索空间。



例如在VRPTW中,算法执行过程中记录下每一个客户点被移除和插入的次数和频率,从而有针对性地探索广度更大的搜索空间。


具体来说,分散搜索有以下几种方法:




3.1 重启分散法(Restart diversification)


这种方法的思想为:迫使当前解或者最优解中某些使用频率较少的邻域动作被使用来进行当前解的优化,例如在VRPTW中,将某些不经常被移除的客户点强制进行移除和插入操作。



3.2 连续分散法(Continuous diversification)


这种方法设法将分散融入到搜索过程之中,其通过将邻域动作使用频率作为目标函数的一部分,以此来选择可能的更优的操作。


3.3 战略震荡(Strategy oscillation)


这种方法的核心在于允许邻域动作产生不可行解。其方法为:放松问题的某些约束,在产生一个新的解之后,衡量这个新的解对于放松的这些约束的违反程度,将违反程度通过一个惩罚项添加到目标函数之中。但这样做的关键决策之处为:需要仔细确定惩罚项的权重,这是很难进行有效确定的。为了规避这个问题,一般采用自适应惩罚权重的调整方法:当前几次迭代之中只出现的不可行解,则相关违背约束的惩罚权重增加;当前几次迭代的解均是可行解,则相关约束的惩罚权重减小。



通过惩罚权重的自适应改变和调整,会引导搜索在可行解和不可行解之间来回穿梭,因此增加了搜索的分散性。



4、TS求解VRPTW的代码


本文使用C++编程实现了TS求解VRPTW问题,代码已上传至GitHub:

https://github.com/Dragon-wang-fei-long/Huristic-code/tree/Dragon-wang-fei-long-heuristic-TS

相关文章
|
1月前
|
编译器 C++ 开发者
【C++】深入解析C/C++内存管理:new与delete的使用及原理(三)
【C++】深入解析C/C++内存管理:new与delete的使用及原理
|
5天前
|
算法 安全 C++
提高C/C++代码的可读性
提高C/C++代码的可读性
19 4
|
1月前
|
C++
C++番外篇——虚拟继承解决数据冗余和二义性的原理
C++番外篇——虚拟继承解决数据冗余和二义性的原理
39 1
|
1月前
|
Linux C语言 C++
vsCode远程执行c和c++代码并操控linux服务器完整教程
这篇文章提供了一个完整的教程,介绍如何在Visual Studio Code中配置和使用插件来远程执行C和C++代码,并操控Linux服务器,包括安装VSCode、安装插件、配置插件、配置编译工具、升级glibc和编写代码进行调试的步骤。
195 0
vsCode远程执行c和c++代码并操控linux服务器完整教程
|
1月前
|
存储 算法 C++
【搜索算法】 跳马问题(C/C++)
【搜索算法】 跳马问题(C/C++)
|
1月前
|
人工智能 算法 Java
【搜索算法】数字游戏(C/C++)
【搜索算法】数字游戏(C/C++)
|
6天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
29 4
|
7天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
26 4
|
30天前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
27 4
|
30天前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
23 4