用遗传算法求解旅行商问题

2023-05-16

以下是用遗传算法解决旅行商问题的实验报告

1.问题描述

旅行商问题(Travelling Salesman Problem, 简记TSP,亦称货郎担问题):设有n个城市和距离矩阵D=[dij],其中dij表示城市i到城市j的距离,i,j=1,2 … n,则问题是要找出遍访每个城市恰好一次的一条回路并使其路径长度为最短。

 

2.算法设计

遗传算法(GeneticAlgorithm)是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,通过模拟自然进化过程搜索最优解。遗传算法是从首先是初始化一个种群,然后根据适应性函数确定个体的适应度,由适应度来选择个体进行交叉,以某种概率让个体进行变异,从而不断选出适应度高的个体,进而更新种群。

算法流程图如下图所示。

其中

(1)城市个数选择中国34个省会城市坐标,种群规模设置为100,变异概率设置为0.01,迭代次数初步设置为5000。

(2)个体适应度代表的是34个城市连成路线的欧式距离。

(3)选择个体进行交叉操作的时候采用轮盘赌策略。

qa 表示个体a的累积概率,如上图所示个体1、2、3、4的累积概率分别为0.14、0.53、0.6,1。随机生成一个0到1的浮点数f,若 qa < f <= qb,则个体b被选中。当采用轮盘赌策略选择交叉父体之后,采用顺序交叉法进行交叉操作:

       

(3)变异操作对每一个个体以变异概率确定是否变异,如果变异的话,随机在个体中选择两个城市,然后交换这两个城市的位置得到变异的效果。

(4)产生新的个体之后,采用精英保留策略,即适应度最好的20个体会被保留下来,其他个体按照适应度进行保留。

 

3.程序流程

1.初始化城市序列的坐标

2.用欧式距离计算城市序列中每个个体的适应度

3.根据适应度来选择个体作为交叉操作的父体,选择完之后用顺序交叉来进行交叉操作

4.以一定的变异个体才确定是否对个体进行变异,如果需要进行变异,侧随机选择个体的两个城市进行交换。

5.选择适应度最好的20个个体直接保留到下一代,下一代的其他个体按照个体的适应度进行选择。

6.判断是否达到迭代次数,如果没有转到第2步,达到的话转到第7步。

7.输出适应度最好的个体。

 

4.代码

import numpy as np
import random
import copy
import matplotlib.pyplot as plt

class City:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return "(" + str(self.x) + "," + str(self.y) + ")"

def distance(ca, cb):
    dx = abs(ca.x - cb.x)
    dy = abs(ca.y - cb.y)
    distance = np.sqrt((dx ** 2) + (dy ** 2))
    return distance

def init_pop(city_list, popSize):
    pop = []
    for i in range(popSize):
        new_city_list = random.sample(city_list, len(city_list))
        pop.append(new_city_list)

    return pop

def fitness(pop):
    dis_citys = distance_citys(pop)
    return 1.0/dis_citys

def distance_citys(pop):
    temp_dis = 0
    for i in range(len(pop)-1):
        temp_dis += distance(pop[i], pop[i+1])
    temp_dis += distance(pop[len(pop)-1], pop[0])

    return temp_dis

def rank(poplulation):
    rankPop_dic = {}
    for i in range(len(poplulation)):
        fit = fitness(poplulation[i])
        rankPop_dic[i] = fit

    return sorted(rankPop_dic.items(), key=lambda x:x[1], reverse=True)


def select(pop, pop_rank, eliteSize):
    select_pop = []
    for i in range(eliteSize):
        select_pop.append(pop[pop_rank[i][0]])

    cumsum = 0
    cumsum_list = []
    temp_pop = copy.deepcopy(pop_rank)
    for i in range(len(temp_pop)):
        cumsum += temp_pop[i][1]
        cumsum_list.append(cumsum)
    for i in range(len(temp_pop)):
        cumsum_list[i] /= cumsum

    for i in range(len(temp_pop)-eliteSize):
        rate = random.random()
        for j in range(len(temp_pop)):
            if cumsum_list[j] > rate:
                select_pop.append(pop[pop_rank[i][0]])
                break

    return select_pop

def breed(pop, eliteSize):
    breed_pop = []
    for i in range(eliteSize):
        breed_pop.append(pop[i])

    i = 0
    while i < (len(pop)-eliteSize):
        a = random.randint(0, len(pop)-1)
        b = random.randint(0, len(pop)-1)
        if a != b:
            fa, fb = pop[a], pop[b]
            genea, geneb = random.randint(0, len(pop[a])-1), random.randint(0, len(pop[b])-1)
            startgene = min(genea, geneb)
            endgene = max(genea, geneb)
            child1 = []
            for j in range(startgene, endgene):
                child1.append(fa[j])
            # child1 = copy.deepcopy(fa[:-1])
            child2 = []
            for j in fb:
                if j not in child1:
                    child2.append(j)
            # child2 = [j for j in fb if j not in child1]
            breed_pop.append(child1+child2)
            i = i+1

    return breed_pop

def mutate(pop, mutationRate):
    mutation_pop = []
    for i in range(len(pop)):
        for j in range(len(pop[i])):
            rate = random.random()
            if rate < mutationRate:
                a = random.randint(0, len(pop[i])-1)
                pop[i][a], pop[i][j] = pop[i][j], pop[i][a]
        mutation_pop.append(pop[i])

    return mutation_pop


def next_pop(population, eliteSize, mutationRate):
    pop_rank = rank(population) #按照适应度排序
    select_pop = select(population, pop_rank, eliteSize) #精英选择策略,加上轮盘赌选择
    breed_pop = breed(select_pop, eliteSize) #繁殖
    next_generation = mutate(breed_pop, mutationRate) #变异

    return next_generation

#画出路线图的动态变化
def GA_plot_dynamic(city_list, popSize, eliteSize, mutationRate, generations):
    plt.figure('Map')
    plt.ion()
    population = init_pop(city_list, popSize)

    print("initial distance:{}".format(1.0/(rank(population)[0][1])))
    for i in range(generations):
        plt.cla()
        population = next_pop(population, eliteSize, mutationRate)
        idx_rank_pop = rank(population)[0][0]
        best_route = population[idx_rank_pop]
        city_x = []
        city_y = []
        for j in range(len(best_route)):
            city = best_route[j]
            city_x.append(city.x)
            city_y.append(city.y)
        city_x.append(best_route[0].x)
        city_y.append(best_route[0].y)
        plt.scatter(city_x, city_y, c='r', marker='*', s=200, alpha=0.5)
        plt.plot(city_x, city_y, "b", ms=20)
        plt.pause(0.1)

    plt.ioff()
    plt.show()



    print("final distance:{}".format(1.0 / (rank(population)[0][1])))
    bestRouteIndex = rank(population)[0][0]
    bestRoute = population[bestRouteIndex]
    return bestRoute

def GA(city_list, popSize, eliteSize, mutationRate, generations):
    population = init_pop(city_list, popSize) #初始化种群
    process = []

    print("initial distance:{}".format(1.0/(rank(population)[0][1])))
    for i in range(generations):
        population = next_pop(population, eliteSize, mutationRate) #产生下一代种群
        process.append(1.0 / (rank(population)[0][1]))



    plt.figure(1)
    print("final distance:{}".format(1.0 / (rank(population)[0][1])))
    plt.plot(process)
    plt.ylabel('Distance')
    plt.xlabel('Generation')
    plt.savefig(str(generations)+ '_' + str(1.0 / (rank(population)[0][1])) + '_' + str(mutationRate) +'_process.jpg')

    plt.figure(2)
    idx_rank_pop = rank(population)[0][0]
    best_route = population[idx_rank_pop]
    city_x = []
    city_y = []
    for j in range(len(best_route)):
        city = best_route[j]
        city_x.append(city.x)
        city_y.append(city.y)
    city_x.append(best_route[0].x)
    city_y.append(best_route[0].y)
    plt.scatter(city_x, city_y, c='r', marker='*', s=200, alpha=0.5)
    plt.plot(city_x, city_y, "b", ms=20)

    plt.savefig(str(generations)+'_' + str(mutationRate) + '_route.jpg')
    plt.show()

num_city = 25
city_list = []


# for i in range(num_city):
#     x = random.randint(1, 200)
#     y = random.randint(1, 200)
#     city_list.append(City(x, y))
with open('city.txt', 'r', encoding='UTF-8') as f:
    lines = f.readlines()
for line in lines:
    line = line.replace('\n', '')
    # line = line.replace('\t', '')
    city = line.split('\t')
    city_list.append( City( float(city[1]), float(city[2]) ) )

# mutationRates = [0.001, 0.002, 0.005, 0.008, 0.01, 0.02]
# for mut in mutationRates:
GA(city_list, 100, 20, 0.01, 5000)

5.代码运行及测试

在城市个数即中国省会城市个数为34,种群大小为100,选择20个精英进行保留,采用轮盘赌选择交叉父体,顺序交叉方式进行交叉操作.

变异概率为0.01的情况下。对迭代次数500,800,1000,3000,5000,6000进行了测试。

 

根据实验结果可以看出,随着次数的增加,城市之间的路线距离也在逐渐的下降,而且可以发现,在迭代次数增加的前期,比如500增到100的时候,城市之间的距离下降的比较快,而在于迭代次数增加的后期比如5000-6000,距离基本没有明显的下降了,而是趋于平稳,可以证明本实验设计的算法大概在5000次左右就可以收敛到一个蛮好的结果。

最后,由于遗传算法是一个启发式的算法,存在一定的偶然性,有时候可能会陷入到一种局部最优解中,比如我在实验中有时候尝试次数为7000次的时候,效果反而没有之前的好。这也说明了并不是迭代次数越多越好。

 

在迭代次数为1000的情况下,依次对变异率0.001,0.002,0.005,0.008,.0.01,0.02进行了测试:

 

根据实验结果可以看出,当变异率从0.001逐渐增大的时候,城市之间的路径连线在得到不断的优化,即最短路径长度逐渐变短。当变异率逐渐增大到0.008以后,进一步增大到0.1甚至0.2的时候发现路径反而变差了,即最短路径长度再次出现增大的情况,而且根据迭代曲线可以看出,在迭代的过程中出现了明显的震荡,证明了过高的变异率会导致遗传算法的不稳定性,从而使得算法陷入到了一个局部最优解中。

所以可以看出变异率对于遗传算法的最终效果显得尤为重要,过小或者过大都不是一种好的选择,根据实验结果0.008左右会是一个不错的参考值。

 

6.结论

旅行商问题属于NP难问题,无法在线性的复杂度中求解。利用遗传算法这种启发式算法可以在相对短的时间内找到一个相对优化的解。而且在实验中我们发现,一些超参数的设置,比较变异率,种群大小,迭代次数对于最终的结果都是至关重要的。另外对于一些策略,比如交叉策略,精英保留策略的运用对于产生一个好的解显得尤为重要。

参考:

https://zhuanlan.zhihu.com/p/41292727

https://blog.csdn.net/greedystar/article/details/80343841

 

附录:

程序中选择的数据为中国省会城市坐标

北京	 116.46	39.92
天津 	117.2	39.13
上海 	121.48	31.22
重庆 	106.54	29.59
拉萨	 91.11	29.97
乌鲁木齐 	87.68	43.77
银川 	106.27	38.47
呼和浩特 	111.65	40.82
南宁	 108.33	22.84
哈尔滨	126.63	45.75
长春 	125.35	43.88
沈阳 	123.38	41.8
石家庄	114.48	38.03
太原	 112.53	37.87
西宁 	101.74	36.56
济南	 117	36.65
郑州	 113.6	34.76
南京 	118.78	32.04
合肥 	117.27	31.86
杭州 	120.19	30.26
福州 	119.3	26.08
南昌	 115.89	28.68
长沙	 113	28.21
武汉	 114.31	30.52
广州	 113.23	23.16
台北	 121.5	25.05
海口	 110.35	20.02
兰州 	103.73	36.03
西安 	108.95	34.27
成都 	104.06	30.67
贵阳 	106.71	26.57
昆明	 102.73	25.04
香港	 114.1	22.2
澳门 	113.33	22.13

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

用遗传算法求解旅行商问题 的相关文章

随机推荐

  • px4+vins+ego单机鲁棒飞行三(realsense_ros配置及经验篇)

    px4 43 vins 43 ego单机鲁棒飞行三 xff08 realsense ros配置及经验篇 xff09 一 驱动及realsense ros安装二 参数设置三 经验 一 驱动及realsense ros安装 D435i标定摄像头
  • px4+vins+ego单机鲁棒飞行四(PX4飞控日志分析篇)

    px4 43 vins 43 ego单机鲁棒飞行四 xff08 PX4飞控日志分析篇 xff09 一 FlightPlot安装二 记录日志二 取出日志三 分析日志 一 FlightPlot安装 参考博客 参考视频 二 记录日志 在QGC中参
  • px4+vins+ego单机鲁棒飞行二-1(更改px4外部视觉估计固件)

    px4 43 vins 43 ego单机鲁棒飞行二 1 xff08 更改px4外部视觉估计固件 xff09 一 EKF2源码 获取视觉里程计信息二 EKF2源码 设置外部视觉数据三 源码中对位置的发送四 测试 前提 xff1a 固件1 11
  • px4+vins+ego单机鲁棒飞行五(坐标系变换篇)

    px4 43 vins 43 ego单机鲁棒飞行五 xff08 坐标系变换篇 xff09 一 齐次矩阵变换原理二 无人机上利用旋转矩阵求飞机中心位置 一 齐次矩阵变换原理 参考一 参考二 二 无人机上利用旋转矩阵求飞机中心位置 首先写出相机
  • 编译多版本opencv,并在cmakelists中链接

    编译多版本opencv xff0c 并在cmakelists中链接 一 下载二 编译三 链接四 替代系统的 xff08 可选 xff0c 但不建议 xff09 五 链接了 xff0c 但无法找到 一 下载 github链接 自己选择版本 x
  • CMakeLists笔记

    CMakeLists笔记 一 路径名二 函数三 常用 一 路径名 PROJECT SOURCE DIR xff1a 一般为catkin ws src xff0c 是cmakelists的绝对路径PROJECT BINARY DIR xff1
  • 源码编译安装openvino

    源码编译安装openvino 1 原地升级cmake2 编译opencv4 5 33 下载openvino4 配置usb规则 参考博客 交叉编译方式 1 原地升级cmake 方法一 xff1a 下载3 19 0中的CMake 3 19 0
  • 【ros】读取串口数据

    文章目录 一 自定义 gnrmc msg二 代码三 结果四 注意点 有时候 有的设备是通过串口发送数据 xff0c 想要在 ros 中 xff0c 读取串口数据 xff0c 记录一下操作 xff1a 一 自定义 gnrmc msg 首先需要
  • Android守护进程

    守护进程 守护进程 一直在后台运行的进程 本文主要讲解一些android比较常用的守护进程的方法 实现思想 1 保活 xff0c 通过提高进程优先级 xff0c 降低进程被杀死的概率 2 拉起 xff0c 进程被杀死后 xff0c 进行拉起
  • ros package 由于依赖 msg 导致编译问题解决

    文章目录 1 问题2 解决 1 问题 经常我们会自定义一些 msg 给其他的 package 使用 如果正常写 CmakeLists txt 在编译的时候 就会提示没有找到依赖的 msg 需要先编译 msg 的 package 再编译其他的
  • 使用Docker部署软件运行环境

    什么是docker xff1f Docker是基于Go语言进行开发实现 xff0c 一个开源的应用容器引擎 采用Linux内核的cgroup xff0c namespace xff0c 以及AUFS类的Union FS等技术 xff0c 对
  • 【控制control】四足机器人运动学、动力学模型

    系列文章目录 提示 xff1a 这里可以添加系列文章的所有文章的目录 xff0c 目录需要自己手动添加 TODO 写完再整理 文章目录 系列文章目录前言一 四足机器人实际模型的物理难点二 四足机器人运动学模型1 方法一 xff1a DH法建
  • 【项目解读】fast_planner工程解读

    系列文章目录 提示 xff1a 这里可以添加系列文章的所有文章的目录 xff0c 目录需要自己手动添加 TODO 写完再整理 文章目录 系列文章目录前言一 规划系统运行逻辑 业务部分 1 Fast planner node cpp 程序入口
  • IMU方向位姿估计

    系列文章目录 提示 xff1a 这里可以添加系列文章的所有文章的目录 xff0c 目录需要自己手动添加 TODO 写完再整理 文章目录 系列文章目录前言一 方法一 xff1a IMU方向位姿可以直接从IMU本身提供的专有算法中获得 xff0
  • 【autoware的仿真平台】

    系列文章目录 提示 xff1a 这里可以添加系列文章的所有文章的目录 xff0c 目录需要自己手动添加 TODO 写完再整理 文章目录 系列文章目录前言一 仿真的必要性及常见的仿真工具介绍二 gazebo仿真插件介绍及源码解析1 gazeb
  • 【机械臂、无人机规控篇】(8)机械臂轨迹规划、跟踪控制方向

    系列文章目录 提示 xff1a 这里可以添加系列文章的所有文章的目录 xff0c 目录需要自己手动添加 TODO 写完再整理 文章目录 系列文章目录前言一 机械臂的规划控制和无人的规划控制的异同点分析1 规划的异同分析2 控制的异同分析 二
  • 微信支付——支付签名验证失败的坑

    只讲几个微信支付开发中的签名问题 xff08 JAVA版的公众号支付 xff09 第一个是获取订单数据时生成 xff0c 然后通过这些数据生成预支付订单 xff08 通过 统一下单 方法取得 xff09 xff0c 微信官方返回一串xml数
  • c++的多重继承

    一 前言 每个类只继承一个父辈 xff0c 在现实世界中事情通常是这样的 xff0c 但是有一些类却代表两个类的合成 例如两用沙发 xff0c 它是一张床 xff0c 也是一个沙发 二 示例代码 xff0c 用作下面提出问题使用 span
  • 学习 STM32之九轴姿态传感器(BWT901CL)串口通信读取数据

    由于个人应用到3轴传感器 xff0c 所以买了直接买了一个9轴的 xff0c 用于学习STM32Core平台串口2连接维特智能串口Normal协议 xff0c 然后通过串口1直接打印数据 xff0c 接收传感器数据和与传感器进行通信 xff
  • 用遗传算法求解旅行商问题

    以下是用遗传算法解决旅行商问题的实验报告 1 问题描述 旅行商问题 xff08 Travelling Salesman Problem 简记TSP xff0c 亦称货郎担问题 xff1a 设有n个城市和距离矩阵D 61 dij xff0c