启发式算法-禁忌搜索(Python)
简介
禁忌搜索(Tabu Search,TS,又称禁忌搜寻法)是一种现代启发式算法,由美国科罗拉多大学教授Fred Glover在1986年左右提出的,是一个用来跳脱局部最优解的搜索方法。其先创立一个初始化的方案;基于此,算法“移动”到一相邻的方案。经过许多连续的移动过程,提高解的质量。(来源百度)
本文通过解决旅行商问题,实现禁忌搜索算法。
基本概念
邻域(neighborhood)、禁忌表(tabu list)、禁忌长度(tabu length)、候选解(candidate)、藐视准则(aspiration criterion)
算法流程图
旅行商问题-禁忌搜索算法实现
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
# 生成邻接矩阵
np.random.seed(100)
def make_data(dim=6):
d = np.ndarray((dim, dim))
for i in range(dim):
for j in range(i, dim):
if j == i:
d[i, j] = 0
else:
v = np.random.randint(10, 500)
d[i, j] = v
d[j, i] = v
return d
adjacency_atrix = make_data(dim=10)
print(adjacency_atrix)
[[ 0. 18. 290. 333. 369. 353. 89. 442. 404. 360.]
[ 18. 0. 446. 364. 63. 76. 236. 24. 300. 250.]
[290. 446. 0. 290. 153. 238. 373. 326. 68. 410.]
[333. 364. 290. 0. 147. 103. 96. 396. 165. 374.]
[369. 63. 153. 147. 0. 398. 425. 395. 151. 255.]
[353. 76. 238. 103. 398. 0. 221. 110. 14. 101.]
[ 89. 236. 373. 96. 425. 221. 0. 453. 333. 145.]
[442. 24. 326. 396. 395. 110. 453. 0. 59. 441.]
[404. 300. 68. 165. 151. 14. 333. 59. 0. 203.]
[360. 250. 410. 374. 255. 101. 145. 441. 203. 0.]]
class TabuList(list): # 定义禁忌表类
def __init__(self, length, *args, **kwargs):
super().__init__(self, *args, **kwargs)
self.__length = length
def push(self, item):
if len(self) >= self.__length:
self.pop() # 尾部删除
if len(self) == 0:
self.append(item)
else:
self.insert(0, item) # 头部插入
def find(self, item):
return item in self
class TabuSearch():
def __init__(self, tabu_length=3):
self.tabu_list = TabuList(tabu_length) # 禁忌表
def neighborhood_2opt(self, value: list, inplace=True):
# 使用 2 opt 得出临近解
if inplace:
value = value[:]
l = len(value)
if l <= 2:
return value
index1 = np.random.randint(0, l) # 左闭右开
index2 = np.random.randint(0, l)
if l < 5:
value[index1], value[index2] = value[index2], value[index1]
return value
while abs(index1 - index2) <= 1:
index2 = np.random.randint(0, l)
if index1 > index2:
index1, index2 = index2, index1
if index1 == 0 and index2 == l -1:
value[index1], value[index2] = value[index2], value[index1]
return value
while index1 < index2:
value[index1], value[index2] = value[index2], value[index1]
# print(f"==翻转列表: {index1} >> {index2}")
index1 += 1
index2 -= 1
return value
def get_candidate(self,x, n=5):
data = []
for i in range(n):
new_x = self.neighborhood_2opt(x, inplace=True)
data.append(new_x)
return data
def objective_func(self, x:list, adjacency_atrix:np.ndarray):
w = 0
i0 = x[0]
for i in x[1:]:
w += adjacency_atrix[i0, i]
i0 = i
return w
def random_x(self, dim, skip=0):
x = []
while len(x) < dim - 1:
# print(len(x), dim)
v = np.random.randint(0, dim)
if v == skip or v in x:
continue
x.append(v)
return x
def aspiration_criterion(self, value, best_value, n=0, max_n=100):
# 如果当前解,有非常大的优化则破禁
return (value * 2 < best_value) and (n > max_n)
def run(self, adjacency_atrix, start=0, loop=1000):
dim = adjacency_atrix.shape[0]
x = self.random_x(dim, skip=start)
best_x = [start] + x + [start]
best_value = self.objective_func(best_x, adjacency_atrix)
print('初始解:', x)
for n in range(loop):
x_list = self.get_candidate(x)
candidates = [[start] + i + [start] for i in x_list]
candidates = [(i, self.objective_func(i, adjacency_atrix)) for i in candidates]
candidates = sorted(candidates, key=lambda i: i[1], reverse=False) # 小到大排序
b_x = candidates[0]
if b_x[1] < best_value:
best_x = b_x[0]
best_value = b_x[1]
for i, v in candidates:
x = i[1:-1]
if self.tabu_list.find(x): # 在禁忌表中,则忽略该解
if self.aspiration_criterion(v, best_value, n=n):
self.self.tabu_list.push(x)
print(f"==={n} 破禁止 {i}, {v}")
break
# if i == b_x[0]:
# print(f"===={n} 禁止最优 {b_x}")
continue
self.tabu_list.push(x)
break
if n % 50 == 0:
print(f"n: {n} {b_x}")
x = [start] + x + [start]
value = self.objective_func(x, adjacency_atrix)
print(f"best_x: {best_x}, best_value: {best_value} \ncurrent: {x} value: {value}")
tbs = TabuSearch(tabu_length=5)
tbs.run(adjacency_atrix)
初始解: [7, 2, 9, 3, 5, 8, 1, 6, 4]
n: 0 ([0, 7, 2, 4, 6, 1, 8, 5, 3, 9, 0], 2733.0)
n: 50 ([0, 6, 9, 5, 7, 8, 3, 4, 1, 2, 0], 1615.0)
n: 100 ([0, 6, 3, 2, 8, 7, 1, 5, 9, 4, 0], 1427.0)
n: 150 ([0, 9, 5, 2, 4, 3, 6, 8, 7, 1, 0], 1529.0)
n: 200 ([0, 2, 4, 1, 7, 8, 9, 5, 3, 6, 0], 1181.0)
n: 250 ([0, 9, 8, 4, 2, 5, 1, 7, 3, 6, 0], 1786.0)
n: 300 ([0, 9, 5, 7, 1, 4, 8, 2, 3, 6, 0], 1352.0)
n: 350 ([0, 2, 6, 3, 4, 9, 5, 8, 7, 1, 0], 1377.0)
n: 400 ([0, 6, 5, 3, 9, 4, 2, 8, 7, 1, 0], 1364.0)
n: 450 ([0, 6, 5, 3, 7, 8, 9, 2, 4, 1, 0], 1715.0)
n: 500 ([0, 2, 8, 7, 1, 4, 9, 5, 3, 6, 0], 1148.0)
n: 550 ([0, 2, 8, 4, 3, 6, 9, 5, 7, 1, 0], 1150.0)
n: 600 ([0, 1, 7, 8, 4, 2, 5, 3, 6, 9, 0], 1347.0)
n: 650 ([0, 1, 7, 5, 8, 9, 4, 6, 3, 2, 0], 1725.0)
n: 700 ([0, 6, 7, 1, 4, 2, 3, 8, 5, 9, 0], 1712.0)
n: 750 ([0, 1, 4, 7, 5, 9, 8, 2, 6, 3, 0], 1760.0)
n: 800 ([0, 2, 8, 9, 5, 7, 1, 4, 3, 6, 0], 1191.0)
n: 850 ([0, 1, 7, 5, 3, 8, 2, 4, 9, 6, 0], 1130.0)
n: 900 ([0, 4, 3, 2, 8, 1, 7, 5, 9, 6, 0], 1643.0)
n: 950 ([0, 6, 9, 5, 3, 4, 2, 8, 7, 1, 0], 907.0)
best_x: [0, 6, 9, 5, 3, 4, 2, 8, 7, 1, 0], best_value: 907.0
current: [0, 1, 7, 8, 4, 2, 3, 5, 9, 6, 0] value: 1133.0