启发式算法-禁忌搜索(Python)

启发式算法-禁忌搜索(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

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值