python实现大规模邻域搜索(LNS)求解旅行商问题(TSP)

1. 大规模邻域搜索算法

参考《Handbook of Metaheuristics (Third Edition)》中的Large neighborhood search章节, 建议直接阅读英文原版

1.1. LNS定义

大规模邻域搜索(LNS) 属于超大邻域搜索(Very Large-Scale Neighborhood Search, VLNS)的一类,随着算例规模的增大,邻域搜索算法的邻域规模呈指数增长或者当邻域太大而不能在实际中明确搜索时 (the neighborhood it searches grows exponentially with the instance size or if the neighborhood is simply too large to be searched explicitly in practice),我们把这类邻域搜索算法(Neighborhood Search, NS)归类于VLNS;

1.2. LNS邻域

  • 邻域搜索算法关键在于邻域结构的选择,即邻域定义的方式。通常来讲,邻域越大,局部最优解就越好,获得的全局最优解就越好。同时,邻域越大,每次迭代搜索邻域所需的时间也越长。

  • 在大规模邻域搜索算法中,邻域由一种破坏(destroy)和一种修复(repair)算子隐式定义(the neighborhood is defined implicitly by a destroy and a repair method)。destroy算子会破坏当前解的一部分(变成不可行解),repair算子会对被破坏的解进行重建(重新变成可行解),相当于一个邻域动作变换动作。破坏算子通常包含随机性的元素,以便在每次调用destroy方法时破坏解的不同部分(The destroy method typically contains an element of stochasticity such that different parts of the solution are destroyed in every invocation of the method)。

  • x x x的邻域 N ( x ) N(x) N(x)就可以定义为:首先利用destroy算子破坏解 x x x,然后利用repair算子重建解 x x x,从而得到的一系列解的集合(The neighborhood N(x) of a solution x is then defined as the set of solutions that can be reached by first applying the destroy method and then the repair method)。

  • LNS算法不会搜索一个解的整个邻域(entire neighborhood),而只是对该邻域进行采样(samples)搜索; (按照算法流程,只定义一种detory和repair方式,虽然detory里有随机部分,但也不包含整个邻域)

1.3. LNS框架

  • 伪代码

在这里插入图片描述

在LNS中只定义一种破坏和修复算子,所以破坏和修复算子的定义很重要,直接决定了能搜索到的当前解x的邻域。如果邻域太小,那么局部最优解的质量就一般,因为有些空间没搜索到。如果邻域太大,又缺少带点启发式信息方向搜索。所以Ropke在论文《An adaptive large neighborhood search heuristic for the pickup and delivery problem with time windows》定义了多种破坏和修复算子,提出来自适应大规模邻域搜索(Adaptive Large Neighborhood Search, ALNS)的框架,有空总结写一篇关于ALNS的文章。

2. 旅行商问题TSP

  • 旅行商问题(Travelling Salesman Problem, TSP),通俗而言它是指对于给定的一系列城市和每对城市之间的距离,找到访问每一座城市仅一次并回到起始城市的最短回路。建立不同的建模方式会有不同的求解方式,比如Dantzig-Fulkerson-Johnson模型、Miller-Tucker-Zemlin模型、Commodity Flow、最短路径等;(这里不再赘述,注意TSP问题各种形式的变种)

3. python代码示例及结果

  • python代码
import random
import numpy as np
from copy import deepcopy
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']

class Solution:
    # 使用符号编号表示一个访问路径route
    def __init__(self):
        self.route = []
        self.cost = 0  # 解对应的总成本

class Lns_tsp(object):
    def __init__(self, distance, num_node):
        self.distance = distance
        self.num_node = num_node

    def get_route_cost(self, route):
        # 计算成本的函数
        cost = 0
        for i in range(1, len(route)):
            cost += self.distance[route[i-1]][route[i]]
        return cost

    def destroy_operator(self, solution, num_destroy):
        # 破坏算子: 随机选择num_destroy个不重复的破坏点(即删除num_destroy个城市)
        destroy_node_bank = []  # 保存被删除的城市节点
        while len(destroy_node_bank) < num_destroy:
            n = random.randint(0, self.num_node-1)
            while n in destroy_node_bank:
                n = random.randint(0, self.num_node-1)
            destroy_node_bank.append(n)
            solution.route.remove(n)
        return solution, destroy_node_bank

    def repair_operator(self, solution, destroy_node_bank):
        # 修复算子: 贪婪插入,插入到成本最小的位置
        for n in destroy_node_bank:
            #计算将n插入各个位置的成本
            insert_list = np.full(len(solution.route), 0)
            for i in range(0, len(solution.route)):
                insert_list[i] = self.distance[solution.route[i-1]][n] + self.distance[n][solution.route[i]] - self.distance[solution.route[i]][solution.route[i-1]]

            greedy_index = np.where(insert_list == min(insert_list))[0][0]

            solution.route.insert(greedy_index, n)
        return solution

def plot_best_vales_iteration(best_values_record):
    # 绘制最优解随着迭代变化的趋势
    plt.figure()
    plt.plot([i+1 for i in range(len(best_values_record))], best_values_record)
    plt.xlabel('迭代次数')
    plt.ylabel('最优值')
    plt.show()

def plot_route(route, city_location):
    plt.figure()
    # 绘制散点
    x = np.array(city_location)[:, 0]  # 横坐标
    y = np.array(city_location)[:, 1]  # 纵坐标
    plt.scatter(x, y, color='r')
    # 绘制城市编号
    for i, txt in enumerate(range(1, len(city_location) + 1)):
        plt.annotate(txt, (x[i], y[i]))
    # 绘制方向
    x0 = x[route]
    y0 = y[route]
    for i in range(len(city_location) - 1):
        plt.quiver(x0[i], y0[i], x0[i + 1] - x0[i], y0[i + 1] - y0[i], color='b', width=0.005, angles='xy', scale=1,
                   scale_units='xy')
    plt.quiver(x0[-1], y0[-1], x0[0] - x0[-1], y0[0] - y0[-1], color='b', width=0.005, angles='xy', scale=1,
               scale_units='xy')

    plt.xlabel('横坐标')
    plt.ylabel('纵坐标')
    plt.show()

if __name__ == '__main__':
    ############## 算例和参数设置 ############################
    # 城市节点的位置信息,一行代表一个城市的横坐标及纵坐标
    city_location = [[ 94,  99],
           [ 66,  67],
           [ 14,  78],
           [ 95,  56],
           [ 68,   9],
           [ 26,  20],
           [ 51,  67],
           [ 39,  39],
           [  5,  55],
           [ 12,  33],
           [ 55,  85],
           [ 98,  46],
           [ 36,  39],
           [ 65, 100],
           [ 57,  89],
           [ 88,  24],
           [ 53,  96],
           [ 91,  41],
           [ 32,  69],
           [ 38,  38],
           [ 38,  39],
           [ 85, 100],
           [  7,  37],
           [ 85,  96],
           [ 89,  48],
           [ 85,  35],
           [ 32,  29],
           [ 31,  25],
           [ 20,  17],
           [ 75,  21],
           [ 74,  29],
           [  6,  32],
           [ 20,  81],
           [ 62,   1],
           [ 11,  48],
           [  1,  69],
           [ 99,  70],
           [ 20,  27],
           [ 25,  42],
           [  6,  31],
           [ 78,  24],
           [ 42,  39],
           [ 83,  30],
           [ 94,  10],
           [ 90,  37],
           [ 76,  73],
           [  9,  56],
           [ 39,  33],
           [ 74,  15],
           [ 77,  14]]

    num_node = len(city_location)  # 城市节点的数量
    iter_num = 300  # 迭代次数
    random.seed(3)  # 随机种子
    num_destroy = int(num_node*0.2) # 破坏程度

    # 计算距离成本矩阵 distance, 直接使用欧式距离
    distance = np.full((num_node, num_node), 0)
    for i in range(num_node):
        for j in range(num_node):
            distance[i][j] = ((city_location[i][0]-city_location[j][0])**2+(city_location[i][1]-city_location[j][1])**2)**0.5

    ############## 产生初始解 ############################
    solution = Solution()
    solution.route = [i for i in range(num_node)]  # 按照节点编号依次相连构成初始解也可随机产生
    lns = Lns_tsp(distance, num_node)
    solution.cost = lns.get_route_cost(solution.route) # 计算初始解对应的目标成本
    best_solution = deepcopy(solution)  # 初始化最优解=初始解
    best_values_record = [0 for i in range(iter_num)]  # 初始化保存最优解的集合

    ############## 执行LNS ############################
    for n_gen in range(iter_num):
        tem_solution = deepcopy(solution)
        # 执行破坏修复算子,得到临时解
        tem_solution, destroy_node_bank = lns.destroy_operator(tem_solution, num_destroy)
        tem_solution = lns.repair_operator(tem_solution, destroy_node_bank)
        # 计算临时解的目标值
        tem_solution.cost = lns.get_route_cost(tem_solution.route)

        # 接受标准:如果临时解比当前解好,直接接受;且更新最优解
        if tem_solution.cost < best_solution.cost:
            solution = deepcopy(tem_solution)
            best_solution = deepcopy(tem_solution)
        best_values_record[n_gen] = best_solution.cost

    ############## 绘制结果 ############################
    plot_best_vales_iteration(best_values_record)
    plot_route(best_solution.route, city_location)
  • 结果
    在这里插入图片描述

在这里插入图片描述

  • 33
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
邻域搜索(Large Neighborhood Search,LNS)是一种在解决旅行商问题(Traveling Salesman Problem,TSP)中常用的方法。Matlab是一种强大的科学计算软件,可以用来实现LNS算法。 LNS算法首先从一个初始解开始,然后通过对当前解进行邻域操作,不断搜索更好的解。在这个过程中,邻域操作可以是插入、删除、交换等操作。而大邻域搜索即是在这些邻域操作中,选择一组更大的邻域进行搜索。 在使用Matlab实现LNS算法求解TSP问题时,首先需要将TSP问题模型化为数学模型。可以用一个邻接矩阵表示城市之间的距离。然后,需要编写一个函数来计算当前解的代价,即路径长度。接下来,我们可以使用一个循环,不断进行邻域操作和搜索。 在选择邻域操作时,可以根据问题特点选择适合的操作。例如,可以选择随机删除几个城市然后重新插入,或者选择随机交换几个城市的位置。每次进行邻域操作后,都需要计算新解的代价,并与当前最优解进行比较。如果新解更好,则更新最优解;否则,根据一定概率选择是否接受新解。 最后,需要设置终止条件,例如达到一定迭代次数或者在一定时间内没有找到更优解。在终止时,输出最优解和路径长度。 总之,使用Matlab实现邻域搜索方法求解TSP问题,可以通过定义邻域操作、计算代价、设置终止条件等步骤来实现。实施LNS算法可以帮助我们在多个领域中找到高质量的TSP解决方案。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值