赞
踩
完整代码见github。
禁忌搜索(Tabu Search,TS,以下简称TS) 是一种基于邻域搜索策略的元启发式算法,由Fred W. Glover 在1986提出[1],并于1989构建[2][3]。应用于各类组合优化问题。
禁忌搜索的核心思想: 从一个初始解出发,按一系列规则对邻域进行探索,对已搜索过的途径和局部最优解进行记录(建立禁忌表,tabu list),并在进一步的迭代搜索中尽量避开这些对象(不是绝对禁止循环),减少重复搜索,从而保证对不同的有效搜索途径的探索,提高搜索效率。
禁忌搜索涉及到的几个概念。邻域(neighborhood)、禁忌表(tabu list)、禁忌长度(tabu length)、候选解(candidate)、藐视准则(aspiration criterion)(有的也叫特赦规则)
以TSP(Traveling Salesman Problem)问题为例,0号为起始点,需要依次拜访1,2,…10号,最终返回0号,求最短路径。已知各个点坐标位置,各个点的位置如下图所示。解的形式表示为一个列表,如:route=[1,2,3,4,5,6,7,8,9,10].

(这里用是随机生成一系列坐标点。)
邻域(neighborhood):邻域一般定义为由给定转化规则对给定的问题域上每结点进行转化所得到的问题域上结点的集合。那么在这里即1-10号点的排列组合。
候选解(candidate):从邻域中选择若干个目标值或评价值最佳的邻居作为候选解。候选解集合的生成规则一定程度上决定了搜索的方向和邻域大小,十分关键。候选集过大增加计算内存和计算时间,过小容易过早陷入局部最优。候选集的选择一般由邻域中的邻居组成,可以选择所有邻居,也可以选择表现较好的邻居,还可以随机选择几个邻居。这里我们采用两两交换法则生成候选解集合。
禁忌表(tabu list):记录最优候选解和对应元素,这些元素在下次搜索时将不会被考虑。
禁忌长度(tabu length):禁忌表的最大长度。
藐视准则(aspiration criterion):禁忌搜索算法中,迭代的某一步会出现候选集的某一个元素被禁止搜索,但是若解禁该元素,则会使评价函数有所改善,因此我们需要设置一个特赦规则,当满足该条件时该元素从禁忌表中跳出。
评价函数(evaluation):评价解的好坏。在这里即路径距离。
禁忌搜索算法核心问题:
禁忌对象:禁掉谁?根据受禁对象的不同选择,可行解是一禁禁一个;还是一禁禁掉一大片。主要对禁忌范围,及搜索范围有影响
禁忌长度:禁多久?禁掉的东西什么时候放出来?禁忌长度过短,会导致循环;禁忌长度过长,会导致计算时间过长。
候选集: 邻域中可行解的选取?候选集的大小,过大增加计算内存和计算时间,过小过早陷入局部最优。
python代码实现,可直接运行。
from itertools import combinations import os,sys,copy import numpy as np import time import matplotlib.pyplot as plt from GetData import * class Tabu(): def __init__(self,disMatrix,max_iters=50,maxTabuSize=10): """parameters definition""" self.disMatrix = disMatrix self.maxTabuSize = maxTabuSize self.max_iters = max_iters self.tabu_list=[] def get_route_distance(self,route): ''' Description: function to calculate total distance of a route. evaluate function. parameters: route : list return : total distance : folat ''' routes = [0] + route + [0] # add the start and end point total_distance = 0 for i,n in enumerate(routes): if i != 0 : total_distance = total_distance + self.disMatrix[last_pos][n] last_pos = n return total_distance def exchange(self,s1,s2,arr): """ function to Swap positions of two elements in an arr Args: int,int,list s1 : target 1 s2 : target 2 arr : target array Ouput: list current_list : target array """ current_list = copy.deepcopy(arr) index1 , index2 = current_list.index(s1) , current_list.index(s2) # get index current_list[index1], current_list[index2]= arr[index2] , arr[index1] return current_list def generate_initial_solution(self,num=10,mode='greedy'): """ function to get the initial solution,there two different way to generate route_init. Args: num : int the number of points mode : string "greedy" : advance step by choosing optimal one "random" : randomly generate a series number Ouput: list s_init : initial solution route_init """ if mode == 'greedy': route_init=[0] for i in range(num): best_distance = 10000000 for j in range(num+1): if self.disMatrix[i][j] < best_distance and j not in route_init: best_distance = self.disMatrix[i][j] best_candidate = j route_init.append(best_candidate) route_init.remove(0) if mode == 'random': route_init = np.arange(1,num+1) #init solution from 1 to num np.random.shuffle(route_init) #shuffle the list randomly return list(route_init) def tabu_search(self,s_init): """tabu search""" s_best = s_init bestCandidate = copy.deepcopy(s_best) routes , temp_tabu = [] , [] # init routes.append(s_best) while(self.max_iters): self.max_iters -= 1 # Number of iterations neighbors = copy.deepcopy(s_best) for s in combinations(neighbors, 2): sCandidate = self.exchange(s[0],s[1],neighbors) # exchange number to generate candidates if s not in self.tabu_list and self.get_route_distance(sCandidate) < self.get_route_distance(bestCandidate): bestCandidate = sCandidate temp_tabu = s if self.get_route_distance(bestCandidate) < self.get_route_distance(s_best): # record the best solution s_best = bestCandidate if temp_tabu not in self.tabu_list: self.tabu_list.append(temp_tabu) if len(self.tabu_list) > self.maxTabuSize : self.tabu_list.pop(0) routes.append(bestCandidate) return s_best, routes if __name__ == "__main__": np.random.seed(2020) customerNum = 10 # 定义多少个点 data=GetData() tsp_data = data.generate_locations(num_points=customerNum+1,map_size=100) #在100*100的图中,随机生成位置,customerNum+1 多一个depot点 dismatrix = data.get_euclidean_distance_matrix(tsp_data.locations) """ Tabu : disMatrix : the distance matrix from 0 to X , 0 represernt starting and stopping point。 for example: disMatrix = [[0,3,4,... 1,0,5,... 3,5,0,...]] that means the distance from 0 to 0 is 0, from 0 to 1 is 3,... from 1 to 3 is 5.... max_iters : maximum iterations maxTabuSize : maximum iterations """ tsp = Tabu(disMatrix=dismatrix ,max_iters=10,maxTabuSize=10) # two different way to generate initial solution # num : the number of points s_init = tsp.generate_initial_solution(num=customerNum,mode='greedy') # mode = "greedy" or "random" print('init route : ' , s_init) print('init distance : ' , tsp.get_route_distance(s_init)) start = time.time() best_route , routes = tsp.tabu_search(s_init) # tabu search end = time.time() print('best route : ' , best_route) print('best best_distance : ' , tsp.get_route_distance(best_route)) print('the time cost : ',end - start ) # plot the result changes with iterations results=[] for i in routes: results.append(tsp.get_route_distance(i)) plt.plot(np.arange(len(results)) , results) plt.show() # plot the route data.plot_route(tsp_data.locations,[0]+best_route+[0])
(https://blog.csdn.net/DCXY71/article/details/110991670)
如需使用Solomon标准算例,只需更调用GetData.read_solomon() 函数,详细代码见github。
最后求解可以得到:solution=[2, 4, 3, 6, 1, 5, 9, 7, 8, 8] ,total_distance = 395.

对于中小规模TSP的求解,求解速度和解的质量都不错,禁忌表长度似乎对求解速度影响不大。但随着点数增加,求解规模增大,所用时间明显增加,解的质量出现明显下降,且解的质量与给定初始解密切相关。
1.求解时间增加
这里我们采用的是combinations()函数,对列表元素两两组合,将会产生
C
n
2
C_n^2
Cn2种组合,当n变大时,组合数将非常可观,一般可采用随机组合的方式,随交换元素或是随机选择组合,没必要全部遍历。
self.max_iters -= 1 # Number of iterations
neighbors = copy.deepcopy(s_best)
for s in combinations(neighbors, 2): #这里将所有元素两两组合,若点数增加至1000点,每次将循环499500次
sCandidate = self.exchange(s[0],s[1],neighbors)
2.随着规模增加,解的质量出现下降
我们的候选解集合是通过两两交换规则产生,其实还是针对局部邻域进行贪婪的搜索,搜索范围有限,虽然算法通用易实现,且容易理解,但搜索性能完全依赖于邻域结构和初解,随着规模的增加尤其会陷入局部极小而无法保证全局优化型。为了实现全局优化,可尝试结合其他算法思想:例如以可控性概率接受劣解来跳出局部极小,如模拟退火算法;扩大邻域搜索结构,如TSP的2-opt扩展到k-opt。
3.禁忌长度
禁忌长度是禁忌搜索非常关键的参数,其大小直接影响到整个算法的搜索进程和的解的质量。在这里我们设置禁忌长度为一常数,采用先进先出(FIFO)的形式,但当问题比较复杂,规模比较大时,可以考虑让禁忌长度动态的自适应变化。
3.一些细节
设定合适的终止条件:(在这里我们设置了最大迭代次数)
还需要注意的是:
参考文献
[1] Fred Glover (1986). Future Paths for Integer Programming and Links to Artificial Intelligence. Computers and Operations Research. 13 (5): 533–549. doi:10.1016/0305-0548(86)90048-1.
[2] Fred Glover (1989). Tabu Search – Part 1. ORSA Journal on Computing. 1 (2): 190–206. doi:10.1287/ijoc.1.3.190.
[3] Fred Glover (1990). Tabu Search – Part 2. ORSA Journal on Computing. 2 (1): 4–32. doi:10.1287/ijoc.2.1.4.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。