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

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

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

1.1 搜索空间(search space)

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

1.2 邻域结构(neighborhood structure)

\qquad 邻域是全部搜索空间(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 )    1)\; 1)简单的VRPTW邻域结构
\qquad 在当前解 s s s中随机选择一条车辆路径 r n r_n rn中的任意一个客户点 i i i进行移除,之后将移除的客户点 i i i插入到车辆路径 r n r_n rn或者 s s s中的的任意其他一条路径 r m r_m rm中。这种邻域结构的关键之处需要考虑怎样选择客户点 i i i的插入位置:(1)可以随机选择一条路径中的随机一个位置进行插入;(2)使用贪婪思想,计算所有插入可能,选择变动成本最小的插入位置进行插入;(3)使用贪婪+随机的思想,选择某几条路径(2条)进行搜索,选择其中变动成本最小的位置进行插入。
2 )    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 )    3)\; 3)其他复杂的VRPTW邻域结构
\qquad 允许路径之间多个客户点进行同时交换操作。

1.3 禁忌表(tabu)

\qquad 禁忌表是TS区别于传统LS的显著特征,禁忌表的建立是为了阻止算法向着已经探索过的方向进行寻优。禁忌表的存在使得算法能够有效从之前探索果的寻优空间中跳出来,从而增加寻优空间的广度,达到更好的寻优效果。
禁忌表中的禁忌移动采用一种短期记忆的机制,通常采用固定数量的记忆数量进行储存。最常用的禁忌表为:记录当前解之前几步的邻域动作,禁止在当前邻域动作中选择已经存在于禁忌表中的邻域动作。
\qquad 以VRPTW问题为例:假设现在的邻域动作是将客户点 v 1 v_1 v1从路径 r 1 r_1 r1移动到路径 r 2 r_2 r2,那么禁忌表中的禁忌邻域动作可以是禁止将客户点 v 1 v_1 v1从路径 r 2 r_2 r2移动到路径 r 1 r_1 r1,记为: ( v 1 , r 2 , r 1 ) (v_1,r_2,r_1) (v1,r2,r1),但这种禁忌约束不强,因为一旦客户点 v 1 v_1 v1又从路径路径 r 2 r_2 r2移动到路径 r 3 r_3 r3,那么客户点就可能从路径 r 3 r_3 r3重新移回路径 r 1 r_1 r1;较强的禁忌表形式为:禁止客户点 v 1 v_1 v1移动回路径 r 1 r_1 r1,记为: ( v 1 , r 1 ) (v_1,r_1) (v1,r1),即忽略当前解的影响;更强的禁忌表形式为:要求客户点 v 1 v_1 v1在接下来的几步之中禁止向任何路径进行移动,即: ( v 1 ) (v_1) (v1)

1.4 解禁标准(Aspiration criteria)

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

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

\qquad 定义 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所示。

在这里插入图片描述

2.1 终止准则

\qquad 终止准则可以参考以下几种:
1) 固定计算次数或者CPU计算时间
2) 连续一定次数没有对最优解进行优化
3) 目标函数值达到一定的阈值标准

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

\qquad 由于TS的本质仍是LS,所以TS本身具有容易陷入局部最优的算法缺陷。为了克服或者弥补这一个缺陷,提出分散搜索的算法机制。分散搜索机制通常基于“长记忆”来完成,例如在频率记忆(frequency memory)中,算法执行过程中会记录下不同的解成分在当前解中出现的迭代次数之和,并应用这个信息来进一步扩大搜索空间。
\qquad 例如在VRPTW中,算法执行过程中记录下每一个客户点被移除和插入的次数和频率,从而有针对性地探索广度更大的搜索空间。
具体来说,分散搜索有以下几种方法:

3.1 重启分散法(Restart diversification)

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

3.2 连续分散法(Continuous diversification)

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

3.3 战略震荡(Strategy oscillation)

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

4、TS求解VRPTW的代码

\qquad 本文使用C++编程实现了TS求解VRPTW问题,代码已上传至GitHub:
https://github.com/Dragon-wang-fei-long/Huristic-code/tree/Dragon-wang-fei-long-heuristic-TS

  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
//定义节点类 class Node{ int id; //节点ID int color; //节点颜色 List<Node> neighbors; //邻居节点列表 public Node(int id){ this.id = id; this.color = -1; //初始时节点颜色为-1 neighbors = new ArrayList<Node>(); } //添加邻居节点 public void addNeighbor(Node neighbor){ neighbors.add(neighbor); } } //定义图类 class Graph{ List<Node> nodes; //节点列表 int maxColors; //最大颜色数 public Graph(int maxColors){ this.maxColors = maxColors; nodes = new ArrayList<Node>(); } //添加节点 public void addNode(Node node){ nodes.add(node); } } //定义禁忌搜索类 class TabuSearch{ int tabuTenure; //禁忌期 int maxIterations; //最大迭代次数 Graph graph; //图 int[][] tabuList; //禁忌表 int[] bestSolution; //最优解 int bestObjective; //最优目标函数值 public TabuSearch(int tabuTenure, int maxIterations, Graph graph){ this.tabuTenure = tabuTenure; this.maxIterations = maxIterations; this.graph = graph; tabuList = new int[graph.nodes.size()][graph.maxColors]; bestSolution = new int[graph.nodes.size()]; bestObjective = Integer.MAX_VALUE; } //初始化解 private void initialSolution(){ for(Node node : graph.nodes){ int color = (int)(Math.random() * graph.maxColors); //随机分配颜色 node.color = color; bestSolution[node.id] = color; } bestObjective = evaluateSolution(bestSolution); //计算目标函数值 } //计算目标函数值 private int evaluateSolution(int[] solution){ int conflicts = 0; for(Node node : graph.nodes){ for(Node neighbor : node.neighbors){ if(solution[node.id] == solution[neighbor.id]){ //如果节点颜色与邻居节点颜色相同 conflicts++; } } } return conflicts; } //选择邻域解 private int[] selectNeighbor(int[] solution, int[][] tabuList){ int[] bestNeighbor = null; int bestNeighborObjective = Integer.MAX_VALUE; for(int i = 0; i < graph.nodes.size(); i++){ Node node = graph.nodes.get(i); for(int j = 0; j < graph.maxColors; j++){ if(solution[node.id] != j){ //尝试将节点颜色改为j int[] neighbor = solution.clone(); neighbor[node.id] = j; int neighborObjective = evaluateSolution(neighbor); if(neighborObjective < bestNeighborObjective){ //选择目标函数值最小的邻域解 if(tabuList[node.id][j] == 0){ //如果邻域解不在禁忌表中,则直接选择该邻域解 bestNeighbor = neighbor; bestNeighborObjective = neighborObjective; }else{ //如果邻域解在禁忌表中,则选择禁忌期结束时间最早的邻域解 if(tabuList[node.id][j] < i){ bestNeighbor = neighbor; bestNeighborObjective = neighborObjective; } } } } } } return bestNeighbor; } //更新禁忌表 private void updateTabuList(int[] solution, int[][] tabuList, int iteration){ for(int i = 0; i < graph.nodes.size(); i++){ Node node = graph.nodes.get(i); tabuList[node.id][solution[node.id]] = iteration + tabuTenure; } } //禁忌搜索 public void search(){ initialSolution(); //初始化解 int[] currentSolution = bestSolution.clone(); //当前解 int currentObjective = bestObjective; //当前目标函数值 for(int i = 0; i < maxIterations; i++){ int[] neighbor = selectNeighbor(currentSolution, tabuList); //选择邻域解 int neighborObjective = evaluateSolution(neighbor); //计算邻域解的目标函数值 if(neighborObjective < currentObjective){ //如果邻域解的目标函数值优于当前解,则选择邻域解作为当前解 currentSolution = neighbor; currentObjective = neighborObjective; if(neighborObjective < bestObjective){ //如果邻域解的目标函数值优于最优解,则更新最优解 bestSolution = neighbor.clone(); bestObjective = neighborObjective; } } updateTabuList(currentSolution, tabuList, i); //更新禁忌表 } } } //测试代码 public class Test{ public static void main(String[] args){ Graph graph = new Graph(3); //创建一个最大颜色数为3的图 Node node1 = new Node(0); Node node2 = new Node(1); Node node3 = new Node(2); Node node4 = new Node(3); node1.addNeighbor(node2); node1.addNeighbor(node3); node2.addNeighbor(node1); node2.addNeighbor(node3); node2.addNeighbor(node4); node3.addNeighbor(node1); node3.addNeighbor(node2); node3.addNeighbor(node4); node4.addNeighbor(node2); node4.addNeighbor(node3); graph.addNode(node1); graph.addNode(node2); graph.addNode(node3); graph.addNode(node4); TabuSearch tabuSearch = new TabuSearch(5, 100, graph); //创建一个禁忌搜索对象,禁忌期为5,最大迭代次数为100 tabuSearch.search(); //执行禁忌搜索算法 for(Node node : graph.nodes){ System.out.println("Node " + node.id + " is assigned to color " + tabuSearch.bestSolution[node.id]); } System.out.println("The number of conflicts is " + tabuSearch.bestObjective); } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dragon Fly

多谢老板赏钱[抱拳抱拳抱拳]

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值