首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >基于遗传算法(GA)的TSP(Python实现)

基于遗传算法(GA)的TSP(Python实现)

作者头像
不去幼儿园
发布2024-12-03 12:43:31
发布2024-12-03 12:43:31
6550
举报
文章被收录于专栏:强化学习专栏强化学习专栏

本篇文章是博主在最化优学习、人工智能等领域学习时,用于个人学习、研究或者欣赏使用,并基于博主对相关等领域的一些理解而记录的学习摘录和笔记,若有不当和侵权之处,指出后将会立即改正,还望谅解。文章分类在最优化算法: 最优化算法(3)---《基于遗传算法(GA)的TSP(Python实现)》

基于遗传算法(GA)的TSP(Python实现))

1.项目介绍

基于遗传算法(GA)求解TSP问题是一种常见且有效的方法,它通过模拟进化过程中的选择、交叉和变异等操作,逐步优化解的质量,最终找到较优的旅行路径。在GA算法中,候选解被看作是个体的染色体,并通过适应度函数对每个个体进行评估。在TSP中,适应度函数通常是路径长度的计算,即评估候选解的旅行路径质量。

算法介绍:

遗传算法GA是一种受自然选择和遗传机制启发的优化算法,模拟了生物进化过程中的遗传、变异和选择机制,被广泛应用于解决复杂的优化问题。

在GA算法中,初始群体的生成是一个关键步骤。随机生成的初始群体可能包含低质量的解,但随着进化的进行,通过遗传操作逐渐优化群体中的个体。选择操作通过保留适应度较高的个体来构建下一代群体,从而促进“优良”解的传承。交叉操作模拟了生物间的基因交换,将两个个体的部分基因进行交换以产生新的个体。而变异操作则通过改变个体的染色体,引入新的多样性,有助于跳出局部最优解。

实现GA算法求解TSP问题时,需要合理设置算法的参数,如群体大小、交叉率、变异率等。这些参数会直接影响算法的收敛速度和最终结果。此外,终止条件的设置也至关重要,可以是迭代次数达到预设值或者在连续若干代内没有显著改善时提前结束。

相对于一些传统的穷举或贪婪算法,GA算法具有更好的全局搜索能力,尤其擅长处理高维复杂空间中的优化问题。然而,由于其自适应性和并行性,GA算法也适用于大规模问题的求解。

在Python中实现GA算法求解TSP问题时,通过合适的编码方式代表候选解,定义适应度函数评估解的质量,并结合选择、交叉和变异等操作,可以很好地完成TSP问题的求解。

算法的核心思想包括以下几个方面:
  1. 初始群体:随机生成初始候选解的群体
  2. 个体表示:将每个候选解表示为染色体,通常使用城市序列的编码方式
  3. 适应度函数:评估每个候选解的质量,通常是TSP问题中路径长度的计算
  4. 遗传操作:包括选择、交叉和变异等操作,模拟了生物进化中的遗传机制
GA算法求解TSP的基本思路包括:
  • 初始化:随机生成初始候选解的群体
  • 选择:根据适应度函数对群体中的个体进行选择,保留适应度较高的个体
  • 交叉:通过交叉操作产生新一代的候选解
  • 变异:对交叉后的个体进行变异操作,引入新的变化
  • 终止条件:达到预设的迭代次数或满足特定条件时结束搜索,返回最优路径

通过利用GA算法求解TSP问题,可以有效地寻找到较为优秀的旅行路线,尽管无法保证找到全局最优解,但通常能够获得接近最优解的结果。


2.程序代码

代码语言:javascript
复制
""""
题目:基于遗传算法的TSP
姓名:Rainbook
最终修改时间:2023.12.30
"""
from math import floor
import numpy as np
import time
import matplotlib.pyplot as plt  # 导入所需要的库

plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']  # 使用微软雅黑字体
plt.rcParams['axes.unicode_minus'] = False  # 处理负号显示异常


class Gena_TSP(object):

    def __init__(self, data,
                 maxgen=1000,
                 size_pop=100,
                 cross_prob=0.80,
                 pmuta_prob=0.02,
                 select_prob=0.8):
        self.maxgen = maxgen  # 最大迭代次数
        self.size_pop = size_pop  # 群体个数
        self.cross_prob = cross_prob  # 交叉概率
        self.pmuta_prob = pmuta_prob  # 变异概率
        self.select_prob = select_prob  # 选择概率

        self.data = data  # 城市的左边数据
        self.num = len(data)  # 城市个数 对应染色体长度
        # 距离矩阵n*n, 第[i,j]个元素表示城市i到j距离matrix_dis函数见下文
        self.matrix_distance = self.matrix_dis()

        # 通过选择概率确定子代的选择个数
        self.select_num = max(floor(self.size_pop * self.select_prob + 0.5), 2)

        # 父代和子代群体的初始化(不直接用np.zeros是为了保证单个染色体的编码为整数,np.zeros对应的数据类型为浮点型)
        self.chrom = np.array([0] * self.size_pop * self.num).reshape(self.size_pop,
                                                                      self.num)  # 父 print(chrom.shape)(200, 14)
        self.sub_sel = np.array([0] * int(self.select_num) * self.num).reshape(self.select_num, self.num)  # 子 (160, 14)

        # 存储群体中每个染色体的路径总长度,对应单个染色体的适应度就是其倒数  #print(fitness.shape)#(200,)
        self.fitness = np.zeros(self.size_pop)

        self.best_fit = []  ##最优距离
        self.best_path = []  # 最优路径

    # 计算城市间的距离函数  n*n, 第[i,j]个元素表示城市i到j距离
    def matrix_dis(self):
        res = np.zeros((self.num, self.num))
        for i in range(self.num):
            for j in range(i + 1, self.num):
                res[i, j] = np.linalg.norm(self.data[i, :] - self.data[j, :])  # 求二阶范数 就是距离公式
                res[j, i] = res[i, j]
        return res

    # 随机产生初始化群体函数
    def rand_chrom(self):
        rand_ch = np.array(range(self.num))  ## num 城市个数 对应染色体长度  =14
        for i in range(self.size_pop):  # size_pop  # 群体个数 200
            np.random.shuffle(rand_ch)  # 打乱城市染色体编码
            self.chrom[i, :] = rand_ch
            self.fitness[i] = self.comp_fit(rand_ch)

    # 计算单个染色体的路径距离值,可利用该函数更新fittness
    def comp_fit(self, one_path):
        res = 0
        for i in range(self.num - 1):
            res += self.matrix_distance[one_path[i], one_path[i + 1]]  # matrix_distance n*n, 第[i,j]个元素表示城市i到j距离
        res += self.matrix_distance[one_path[-1], one_path[0]]  # 最后一个城市 到起点距离
        return res

    # 路径可视化函数
    def out_path(self, one_path):
        res = str(one_path[0] + 1) + '-->'
        for i in range(1, self.num):
            res += str(one_path[i] + 1) + '-->'
        res += str(one_path[0] + 1) + '\n'
        print(res)

    # 子代选取,根据选中概率与对应的适应度函数,采用随机遍历选择方法
    def select_sub(self):
        fit = 1. / (self.fitness)  # 适应度函数
        cumsum_fit = np.cumsum(fit)  # 累积求和   a = np.array([1,2,3]) b = np.cumsum(a) b=1 3 6
        pick = cumsum_fit[-1] / self.select_num * (
                    np.random.rand() + np.array(range(int(self.select_num))))  # select_num  为子代选择个数 160
        i, j = 0, 0
        index = []
        while i < self.size_pop and j < self.select_num:
            if cumsum_fit[i] >= pick[j]:
                index.append(i)
                j += 1
            else:
                i += 1
        self.sub_sel = self.chrom[index, :]  # chrom 父

    # 交叉,依概率对子代个体进行交叉操作
    def cross_sub(self):
        if self.select_num % 2 == 0:  # select_num160
            num = range(0, int(self.select_num), 2)
        else:
            num = range(0, int(self.select_num - 1), 2)
        for i in num:
            if self.cross_prob >= np.random.rand():
                self.sub_sel[i, :], self.sub_sel[i + 1, :] = self.intercross(self.sub_sel[i, :], self.sub_sel[i + 1, :])

    def intercross(self, ind_a, ind_b):  # ind_a,ind_b 父代染色体 shape=(1,14) 14=14个城市
        r1 = np.random.randint(self.num)  # 在num内随机生成一个整数 ,num=14.即随机生成一个小于14的数
        r2 = np.random.randint(self.num)
        while r2 == r1:  # 如果r1==r2
            r2 = np.random.randint(self.num)  # r2重新生成
        left, right = min(r1, r2), max(r1, r2)  # left 为r1,r2小值 ,r2为大值
        ind_a1 = ind_a.copy()  # 父亲
        ind_b1 = ind_b.copy()  # 母亲
        for i in range(left, right + 1):
            ind_a2 = ind_a.copy()
            ind_b2 = ind_b.copy()
            ind_a[i] = ind_b1[i]  # 交叉 (即ind_a  (1,14) 中有个元素 和ind_b互换
            ind_b[i] = ind_a1[i]
            x = np.argwhere(ind_a == ind_a[i])
            y = np.argwhere(ind_b == ind_b[i])
            if len(x) == 2:
                ind_a[x[x != i]] = ind_a2[i]  # 查找ind_a 中元素=- ind_a[i] 的索引
            if len(y) == 2:
                ind_b[y[y != i]] = ind_b2[i]
        return ind_a, ind_b

    # 变异模块  在变异概率的控制下,对单个染色体随机交换两个点的位置。
    def mutation_sub(self):
        for i in range(int(self.select_num)):  # 遍历每一个 选择的子代
            if np.random.rand() <= self.cross_prob:  # 如果随机数小于变异概率
                r1 = np.random.randint(self.num)  # 随机生成小于num==可设置 的数
                r2 = np.random.randint(self.num)
                while r2 == r1:  # 如果相同
                    r2 = np.random.randint(self.num)  # r2再生成一次
                self.sub_sel[i, [r1, r2]] = self.sub_sel[i, [r2, r1]]  # 随机交换两个点的位置。

    # 进化逆转  将选择的染色体随机选择两个位置r1:r2 ,将 r1:r2 的元素翻转为 r2:r1 ,如果翻转后的适应度更高,则替换原染色体,否则不变
    def reverse_sub(self):
        for i in range(int(self.select_num)):  # 遍历每一个 选择的子代
            r1 = np.random.randint(self.num)  # 随机生成小于num==14 的数
            r2 = np.random.randint(self.num)
            while r2 == r1:  # 如果相同
                r2 = np.random.randint(self.num)  # r2再生成一次
            left, right = min(r1, r2), max(r1, r2)  # left取r1 r2中小值,r2取大值
            sel = self.sub_sel[i, :].copy()  # sel 为父辈染色体 shape=(1,14)

            sel[left:right + 1] = self.sub_sel[i, left:right + 1][::-1]  # 将染色体中(r1:r2)片段 翻转为(r2:r1)
            if self.comp_fit(sel) < self.comp_fit(self.sub_sel[i, :]):  # 如果翻转后的适应度小于原染色体,则不变
                self.sub_sel[i, :] = sel

    # 子代插入父代,得到相同规模的新群体
    def reins(self):
        index = np.argsort(self.fitness)[::-1]  # 替换最差的(倒序)
        self.chrom[index[:self.select_num], :] = self.sub_sel


def GA_TSP(data):
    Path_short = Gena_TSP(data)  # 根据位置坐标,生成一个遗传算法类
    Path_short.rand_chrom()  # 初始化父类

    # 绘制初始化的路径图
    x = data[:, 0]
    y = data[:, 1]
    print('初始染色体的路程: ' + str(Path_short.fitness[0]))

    # 循环迭代遗传过程
    for i in range(Path_short.maxgen):
        Path_short.select_sub()  # 选择子代
        Path_short.cross_sub()  # 交叉
        Path_short.mutation_sub()  # 变异
        Path_short.reverse_sub()  # 进化逆转
        Path_short.reins()  # 子代插入

        # 重新计算新群体的距离值
        for j in range(Path_short.size_pop):
            Path_short.fitness[j] = Path_short.comp_fit(Path_short.chrom[j, :])

        # 每隔三十步显示当前群体的最优路径
        index = Path_short.fitness.argmin()
        if (i + 1) % 50 == 0:
            # 获取当前时间戳 记录运算时间
            timestamp = time.time()
            formatted_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp))
            print(formatted_time)
            print('第' + str(i + 1) + '代后的最短的路程: ' + str(Path_short.fitness[index]))
            print('第' + str(i + 1) + '代后的最优路径:')
            Path_short.out_path(Path_short.chrom[index, :])  # 显示每一步的最优路径

        # 存储每一步的最优路径及距离
        Path_short.best_fit.append(Path_short.fitness[index])
        Path_short.best_path.append(Path_short.chrom[index, :])

    res1 = Path_short.chrom[0]
    x0 = x[res1]
    y0 = y[res1]
    for i in range(len(data) - 1):
        plt.quiver(x0[i], y0[i], x0[i + 1] - x0[i], y0[i + 1] - y0[i], color='r', 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='r', width=0.005, angles='xy', scale=1,
               scale_units='xy')
    plt.title('基于遗传算法的TSP')
    plt.show()

    return Path_short  # 返回遗传算法结果类


if __name__ == '__main__':
    # 随机生成城市信息
    nCity = 50
    City = np.random.uniform(0, 2000, [nCity, 2])  # uniform()生成nCity个二维数组,数值范围是0到2000
    # 执行算法,绘制最优路径图
    Path_short = GA_TSP(City)
    # print(Path_short)

3.运行结果

参考资料来源:CSDN、百度搜索、维基百科等 文章若有不当和不正确之处,还望理解与指出。由于部分文字、图片等来源于互联网,无法核实真实出处,如涉及相关争议,请联系博主删除。如有错误、疑问和侵权,欢迎评论留言联系作者

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-03-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基于遗传算法(GA)的TSP(Python实现))
    • 1.项目介绍
      • 算法介绍:
      • 算法的核心思想包括以下几个方面:
      • GA算法求解TSP的基本思路包括:
    • 2.程序代码
    • 3.运行结果
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档