遗传算法和禁忌搜索解TSP

原始数据

计算从城市1出发,通过每个城市,最终回到城市1的最短距离,及路径

city_list = [[1, (1150.0, 1760.0)], [2, (630.0, 1660.0)], [3, (40.0, 2090.0)], [4, (750.0, 1100.0)],
             [5, (750.0, 2030.0)], [6, (1030.0, 2070.0)], [7, (1650.0, 650.0)], [8, (1490.0, 1630.0)],
             [9, (790.0, 2260.0)], [10, (710.0, 1310.0)], [11, (840.0, 550.0)], [12, (1170.0, 2300.0)],
             [13, (970.0, 1340.0)], [14, (510.0, 700.0)], [15, (750.0, 900.0)], [16, (1280.0, 1200.0)],
             [17, (230.0, 590.0)], [18, (460.0, 860.0)], [19, (1040.0, 950.0)], [20, (590.0, 1390.0)],
             [21, (830.0, 1770.0)], [22, (490.0, 500.0)], [23, (1840.0, 1240.0)], [24, (1260.0, 1500.0)],
             [25, (1280.0, 790.0)], [26, (490.0, 2130.0)], [27, (1460.0, 1420.0)], [28, (1260.0, 1910.0)],
             [29, (360.0, 1980.0)]]

路径开头和结尾都是1,只要全排列2,…,29看怎么排列路径最短即可。

遗传算法

遗传算法_geatpy
写好代价函数和参数,直接调用geatpy包。

代码

# -*- coding: utf-8 -*-
"""MyProblem.py"""
import geatpy as ea
import numpy as np

city_list = [[1, (1150.0, 1760.0)], [2, (630.0, 1660.0)], [3, (40.0, 2090.0)], [4, (750.0, 1100.0)],
             [5, (750.0, 2030.0)], [6, (1030.0, 2070.0)], [7, (1650.0, 650.0)], [8, (1490.0, 1630.0)],
             [9, (790.0, 2260.0)], [10, (710.0, 1310.0)], [11, (840.0, 550.0)], [12, (1170.0, 2300.0)],
             [13, (970.0, 1340.0)], [14, (510.0, 700.0)], [15, (750.0, 900.0)], [16, (1280.0, 1200.0)],
             [17, (230.0, 590.0)], [18, (460.0, 860.0)], [19, (1040.0, 950.0)], [20, (590.0, 1390.0)],
             [21, (830.0, 1770.0)], [22, (490.0, 500.0)], [23, (1840.0, 1240.0)], [24, (1260.0, 1500.0)],
             [25, (1280.0, 790.0)], [26, (490.0, 2130.0)], [27, (1460.0, 1420.0)], [28, (1260.0, 1910.0)],
             [29, (360.0, 1980.0)]]


x_point_list = [i[1][0] for i in city_list] # x坐标
y_point_list = [i[1][1] for i in city_list] # y坐标


# 计算两城市间的距离
def city_distance(city1, city2):
    distance = ((float(x_point_list[city1] - x_point_list[city2])) ** 2 + (
        float(y_point_list[city1] - y_point_list[city2])) ** 2) ** 0.5
    return distance


# 计算线路路径长度
def aim_func(road_list):
    current_point = 0
    dist = 0
    for i in road_list:
        next_point = i - 1
        dist += city_distance(current_point, next_point)
        current_point = i - 1
    dist += city_distance(current_point, 0)
    return dist


class MyProblem(ea.Problem):  # 继承Problem父类
    def __init__(self):
        name = 'MyProblem'  # 初始化name(函数名称,可以随意设置)
        M = 1  # 初始化M(目标维数)
        maxormins = [1]  # 初始化maxormins(目标最小最大化标记列表,1:最小化该目标;-1:最大化该目标)
        Dim = len(city_list) - 1  # 初始化Dim(决策变量维数)
        varTypes = [1] * Dim  # 初始化varTypes(决策变量的类型,元素为0表示对应的变量是连续的实数;1表示是离散的整数)
        lb = [2] * Dim  # 决策变量下界
        ub = [Dim + 1] * Dim  # 决策变量上界
        lbin = [1] * Dim  # 决策变量下边界,1表示包含上边界;0表示不包含上边界
        ubin = [1] * Dim  # 决策变量上边界

        # 调用父类构造方法完成实例化
        ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb, ub, lbin, ubin)

    def aimFunc(self, pop):  # 目标函数
        X = pop.Phen
        f = np.array([aim_func(road_list) for road_list in X]).reshape(X.shape[0], 1)
        pop.ObjV = f  # 计算目标函数值,赋值给pop种群对象的ObjV属性

# -*- coding: utf-8 -*-
"""main.py"""
import geatpy as ea
from MyProblem import *  # 导入自定义问题接口


# 画线路图
def draw_line_pic(route, cost):
    import matplotlib.pyplot as plt
    x = []
    y = []
    route = list(route)
    route.append(1)
    route.insert(0,1)
    for item in route:
        x.append(x_point_list[item-1])
        y.append(y_point_list[item-1])
    plt.plot(x, y)

    plt.scatter(x_point_list, y_point_list, marker="o", c='g')
    plt.scatter(x_point_list[0], y_point_list[0], marker="o", c="r")
    for i in range(len(city_list)):
        plt.text(x_point_list[i], y_point_list[i], str(i+1), ha='center', va='bottom', fontsize=10)
    plt.title("GA_Search: " + str(cost))
    plt.plot([1,2],[1,2])
    plt.show()

for i in range(1):
    print(str(i)+"次")
    """===============================实例化问题对象================================"""
    problem = MyProblem()  # 生成问题对象
    """==================================种群设置=================================="""
    Encoding = 'P'  # 编码方式

    NIND = 500  # 种群规模
    Field = ea.crtfld(Encoding=Encoding, varTypes=problem.varTypes, ranges=problem.ranges, borders=problem.borders)  # 创建区域描述器
    population = ea.Population(Encoding, Field, NIND)  # 实例化种群对象(此时种群还没被初始化,仅仅是完成种群对象的实例化)
    """================================算法参数设置================================="""
    myAlgorithm = ea.soea_SEGA_templet(problem, population)  # 实例化一个算法模板对象_精英保留的遗传算法
    myAlgorithm.MAXGEN = 500  # 最大进化代数
    myAlgorithm.mutOper.Pm = 0.8  # 变异概率
    myAlgorithm.logTras = 10  # 设置每隔多少代记录日志,若设置成0则表示不记录日志
    myAlgorithm.verbose = True  # 设置是否打印输出日志信息
    myAlgorithm.drawing = 1  # 设置绘图方式(0:不绘图;1:绘制结果图;2:绘制目标空间过程动画;3:绘制决策空间过程动画)
    """===========================调用算法模板进行种群进化==============--==========="""
    [BestIndi, population] = myAlgorithm.run()  # 执行算法模板,得到最优个体以及最后一代种群
    BestIndi.save()  # 把最优个体的信息保存到文件中
    """==================================输出结果=================================="""
    print('用时:%f 秒' % myAlgorithm.passTime)
    print('评价次数:%d 次' % myAlgorithm.evalsNum)

    if BestIndi.sizes != 0:
        print('最优的目标函数值为:%s' % BestIndi.ObjV[0][0])
        print('最优的控制变量值为:')
        road_list=BestIndi.Phen[::]
        print("最优路径:",road_list)
        draw_line_pic(road_list[0], BestIndi.ObjV[0][0])
        finally_route_list = []
        for i in road_list[0]:
            finally_route_list.append([i, (x_point_list[i - 1], y_point_list[i - 1])]) # 最终路径
    else:
        print('没找到可行解。')

结果

用时:10.537014 秒
评价次数:250000 次
最优的目标函数值为:9076.982920396536
最优的控制变量值为:
最优路径: [1 28 6 12 9 26 3 29 5 21 2 20 10 13 4 15 18 14 17 22 11 19 25 7 23
8 27 16 24 1]
在这里插入图片描述

禁忌搜索

TS算法受禁忌表长度、邻域选择方式、以及初始值的影响比较 严重,且经检验极易陷入局部最优值(不知道是不是参数设置原因),且耗时较长。

代码

用python给出TS解TSP问题的代码

# -*- coding: utf-8 -*- 
# @Time : 2022/2/17 15:01 
# @Author : Orange
# @File : ts1.py.py
import copy, random, datetime
import matplotlib.pyplot as plt
import time

city_list = [[1, (1150.0, 1760.0)], [2, (630.0, 1660.0)], [3, (40.0, 2090.0)], [4, (750.0, 1100.0)],
             [5, (750.0, 2030.0)], [6, (1030.0, 2070.0)], [7, (1650.0, 650.0)], [8, (1490.0, 1630.0)],
             [9, (790.0, 2260.0)], [10, (710.0, 1310.0)], [11, (840.0, 550.0)], [12, (1170.0, 2300.0)],
             [13, (970.0, 1340.0)], [14, (510.0, 700.0)], [15, (750.0, 900.0)], [16, (1280.0, 1200.0)],
             [17, (230.0, 590.0)], [18, (460.0, 860.0)], [19, (1040.0, 950.0)], [20, (590.0, 1390.0)],
             [21, (830.0, 1770.0)], [22, (490.0, 500.0)], [23, (1840.0, 1240.0)], [24, (1260.0, 1500.0)],
             [25, (1280.0, 790.0)], [26, (490.0, 2130.0)], [27, (1460.0, 1420.0)], [28, (1260.0, 1910.0)],
             [29, (360.0, 1980.0)]
             ]


class Taboo_search:
    def __init__(self, city_list, candidate_count, taboo_list_length, iteration_count, is_random=True):

        self.city_list = city_list  # 城市列表
        self.candidate_count = candidate_count  # 候选集合长度
        self.taboo_list_length = taboo_list_length  # 禁忌长度
        self.iteration_count = iteration_count  # 迭代次数
        self.min_route, self.min_cost = self.random_first_full_road() if is_random else self.greedy_first_full_road()  # 最小解;最小目标值

        self.taboo_list = []  # 禁忌表

    # 计算两城市间的距离
    def city_distance(self, city1, city2):
        distance = ((float(city1[1][0] - city2[1][0])) ** 2 + (float(city1[1][1] - city2[1][1])) ** 2) ** 0.5
        return distance

    # 获取当前城市邻居城市中距离最短的一个
    def next_shotest_road(self, city1, other_cities):
        tmp_min = 999999
        tmp_next = None
        for i in range(0, len(other_cities)):
            distance = self.city_distance(city1, other_cities[i])
            # print(distance)
            if distance < tmp_min:
                tmp_min = distance
                tmp_next = other_cities[i]
        return tmp_next, tmp_min

    # 随机生成初始线路
    def random_first_full_road(self):
        cities = copy.deepcopy(self.city_list)
        cities.remove(cities[0])
        route = copy.deepcopy(cities)
        random.shuffle(route)
        # route = [[6, (1030.0, 2070.0)], [5, (750.0, 2030.0)], [29, (360.0, 1980.0)], [3, (40.0, 2090.0)],
        #          [26, (490.0, 2130.0)], [9, (790.0, 2260.0)], [12, (1170.0, 2300.0)], [28, (1260.0, 1910.0)],
        #          [8, (1490.0, 1630.0)], [27, (1460.0, 1420.0)], [24, (1260.0, 1500.0)], [13, (970.0, 1340.0)],
        #          [16, (1280.0, 1200.0)], [23, (1840.0, 1240.0)], [7, (1650.0, 650.0)], [25, (1280.0, 790.0)],
        #          [19, (1040.0, 950.0)], [11, (840.0, 550.0)], [22, (490.0, 500.0)], [17, (230.0, 590.0)],
        #          [14, (510.0, 700.0)], [18, (460.0, 860.0)], [15, (750.0, 900.0)], [4, (750.0, 1100.0)],
        #          [10, (710.0, 1310.0)], [20, (590.0, 1390.0)], [2, (630.0, 1660.0)],
        #          [21, (830.0, 1770.0)]]  # 将初始值设置为遗传算法的最优值

        cost = self.route_cost(route)
        return route, cost

    # 根据贪婪算法获取初始线路
    def greedy_first_full_road(self):
        remain_city = copy.deepcopy(self.city_list)
        current_city = remain_city[0]
        road_list = []
        remain_city.remove(current_city)
        all_distance = 0
        while len(remain_city) > 0:
            next_city, distance = self.next_shotest_road(current_city, remain_city)
            all_distance += distance
            road_list.append(next_city)
            remain_city.remove(next_city)
            current_city = next_city
        all_distance += self.city_distance(self.city_list[0], road_list[-1])
        return road_list, round(all_distance, 2)

    # 随机交换2个城市位置
    def random_swap_2_city(self, route):
        # print(route)
        road_list = copy.deepcopy(route)
        two_rand_city = random.sample(road_list, 2)
        # print(two_rand_city)
        index_a = road_list.index(two_rand_city[0])
        index_b = road_list.index(two_rand_city[1])
        road_list[index_a] = two_rand_city[1]
        road_list[index_b] = two_rand_city[0]
        return road_list, sorted(two_rand_city)

    # 计算线路路径长度
    def route_cost(self, route):
        road_list = copy.deepcopy(route)
        current_city = self.city_list[0]
        while current_city in road_list:
            road_list.remove(current_city)
        all_distance = 0
        while len(road_list) > 0:
            distance = self.city_distance(current_city, road_list[0])
            all_distance += distance
            current_city = road_list[0]
            road_list.remove(current_city)
        all_distance += self.city_distance(current_city, self.city_list[0])
        return round(all_distance, 2)

    # 获取下一条线路
    def single_search(self, route):
        # 生成候选集合列表和其对应的移动列表
        candidate_list = []
        candidate_move_list = []
        while len(candidate_list) < self.candidate_count: # 在候选集合里放candidate_count条不重复路径
            tmp_route, tmp_move = self.random_swap_2_city(route)
            # print("tmp_route:",tmp_route)
            if tmp_route not in candidate_list:
                candidate_list.append(tmp_route)
                candidate_move_list.append(tmp_move)
        # 计算候选集合各路径的长度
        candidate_cost_list = []
        for candidate in candidate_list:
            candidate_cost_list.append(self.route_cost(candidate))
        # print(candidate_list)

        min_candidate_cost = min(candidate_cost_list)  # 候选集合中最短路径
        min_candidate_index = candidate_cost_list.index(min_candidate_cost)
        min_candidate = candidate_list[min_candidate_index]  # 候选集合中最短路径对应的线路
        move_city = candidate_move_list[min_candidate_index]

        if min_candidate_cost < self.min_cost:
            # 若满足这个条件不管禁忌对象是否在禁忌表内,都直接更新禁忌表
            self.min_cost = min_candidate_cost
            self.min_route = min_candidate
            if move_city in self.taboo_list:  # 破禁法则,当此移动导致的值更优,则无视该禁忌列表
                self.taboo_list.remove(move_city)
            if len(self.taboo_list) >= self.taboo_list_length:  # 判断该禁忌列表长度是否以达到限制,是的话移除最初始的move
                self.taboo_list.remove(self.taboo_list[0])
            self.taboo_list.append(move_city)  # 将该move加入到禁忌列表
            return min_candidate

        else:
            # 当未找到更优路径时,选择次优路线,如果该次优路线在禁忌表里,则更次一层,依次类推,找到一条次优路线
            if move_city in self.taboo_list:
                tmp_min_candidate = min_candidate
                tmp_move_city = move_city

                while move_city in self.taboo_list:  # 若候选最优禁忌对象已经在T表,寻找次优禁忌对象,若已在T表,.....
                    candidate_list.remove(min_candidate)
                    candidate_cost_list.remove(min_candidate_cost)
                    candidate_move_list.remove(move_city)

                    min_candidate_cost = min(candidate_cost_list)  # 候选集合中次优路径
                    min_candidate_index = candidate_cost_list.index(min_candidate_cost)
                    min_candidate = candidate_list[min_candidate_index]  # 候选集合中最短路径对应的线路
                    move_city = candidate_move_list[min_candidate_index]
                    if len(candidate_list) < 10:  # 防止陷入死循环,在候选集个数小于10的时候跳出
                        min_candidate = tmp_min_candidate
                        move_city = tmp_move_city
            if len(self.taboo_list) >= self.taboo_list_length:  # 判断该禁忌列表长度是否以达到限制,是的话移除最初始的move
                self.taboo_list.remove(self.taboo_list[0])
            self.taboo_list.append(move_city)
            return min_candidate

    # 进行taboo_search直到达到终止条件:循环100次
    def taboo_search(self):
        route = copy.deepcopy(self.min_route)
        for i in range(self.iteration_count):
            route = self.single_search(route)
        new_route = [self.city_list[0]]
        new_route.extend(self.min_route)
        new_route.append(self.city_list[0])  # 前后插入首个城市信息
        return new_route, self.min_cost


# 画线路图
def draw_line_pic(route, cost, duration, desc):
    x = []
    y = []
    for item in route:
        x.append(item[1][0])
        y.append(item[1][1])
    x_org = []
    y_org = []
    point_org = []
    for item in city_list:
        x_org.append(item[1][0])
        y_org.append(item[1][1])
        point_org.append(item[0])
    x0 = [x[0], ]
    y0 = [y[0], ]
    plt.plot(x, y)

    plt.scatter(x_org, y_org, marker="o", c='g')
    plt.scatter(x0, y0, marker="o", c="r")
    for i in range(len(city_list)):
        plt.text(x_org[i], y_org[i], point_org[i], ha='center', va='bottom', fontsize=10)
    plt.title("Taboo_Search(" + desc + ": " + str(cost) + ")")
    plt.show()


if __name__ == "__main__":
    ts_random = Taboo_search(city_list=city_list, candidate_count=40, taboo_list_length=3, iteration_count=4000)
    # ts_greedy = Taboo_search(city_list, candidate_count=40, taboo_list_length=3, iteration_count=4000,
    #                          is_random=False)
    start_time1 = time.time()
    route_random, cost_random = ts_random.taboo_search()
    end_time1 = time.time()
    duration1 = (end_time1 - start_time1)
    # route_greedy, cost_greedy = ts_greedy.taboo_search()
    draw_line_pic(route_random, cost_random, duration1, "random")
    print("最优路径:", [i[0] for i in route_random])
    print("最短距离:", cost_random)
    print("随机TS耗时:",end_time1-start_time1)
    # draw_line_pic(route_greedy, cost_greedy, None, "greedy")

结果

最优路径: [1, 28, 12, 6, 9, 26, 3, 29, 5, 21, 2, 20, 10, 13, 4, 15, 18, 14, 17, 22, 11, 19, 25, 7, 23, 27, 16, 24, 8, 1]
最短距离: 9151.27
随机TS耗时: 43.56393384933472
在这里插入图片描述
注:受初始解和候选表长度的影响,TS结果随机性也比较严重,上面是多次中表现最好的一次了。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

hellobigorange

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值