Python遗传算法工具箱的使用(二)求解最短路径问题

article/2025/10/1 4:02:54

前言

自从上一篇博客详细讲解了Python遗传和进化算法工具箱及其在带约束的单目标函数值优化中的应用之后,我经过不断学习工具箱的官方文档以及对源码的研究,逐步更加地掌握如何利用遗传算法求解更多有趣的问题了。

首先简单回顾一下Python高性能实用型遗传和进化算法工具箱的用法。对于一个优化问题,需要做两个步骤便可进行求解:Step1:自定义问题类;Step2:编写执行脚本调用Geatpy进化算法模板对问题进行求解。在上一篇博客曾“详细”介绍过具体的用法:Python遗传算法工具箱的使用(一)求解带约束的单目标优化_Strong_wind的博客-CSDN博客_python遗传算法工具包,但完整的中文教程可以参考官方文档。

下面切入主题:

本文的“最短路径问题”专指图的最短路径问题,而且只研究单目标的最短路径问题。(实际上最短路径问题还可以是多目标的),参考用书推荐:《网络模型与多目标遗传算法》。这本书我已经上传到此处,可以直接下载到:

网络模型与多目标遗传算法参考资料.zip_网络模型与多目标遗传算法-机器学习文档类资源-CSDN下载

图的最短路径问题从广义上说其实有很多种类型,比如:

  • 确定起点和终点的有向图和无向图的最短路径问题。
  • 旅行商最短路径问题。
  • 车辆配送中的最短路径问题。
  • 物流中心选址的最短路径问题。

其中确定起点和终点的无向图的最短路径问题可以延伸为机器人路径规划问题。也就是说,机器人避开障碍物从起点走向终点的那种路径规划问题本质上是一种复杂一些的“确定起点和终点的无向图的最短路径问题”。

本文主要讲解如何利用遗传算法求解“确定起点和终点的有向图最短路径问题”。对于无向图的,另外再写博客以机器人路径规划作为案例来展开叙述。

正文

问题举例:

以《网络模型与多目标遗传算法》一书中的一个小案例为例,如图所示,当从结点1驶向结点10时,我们经常会考虑怎样选择路径使得花最短的距离达到目的地。此时不需要像旅行商问题(TSP)那样,此类问题不需要要求所有的地点都访问一遍,而只需要想办法用最短的距离从起点走到终点即可。

书中讲述了两种遗传算法编码方式对上述问题进行求解。

法一

利用二进制编码。假设 Xij 是有向图中所有边的一个集合,那么其中一种可行的路径对应的染色体可以是:

其中元素为1表示对应的边被“激活”,即最终路径上包含这条边。于是上面的染色体所代表的路径为:1 → 3 → 6 → 7 → 8 → 10。

这种编码方式最为直观,但有个很大的弊端:在进化过程中染色体往往无法表达一个合法的路径。于是这种情况下需要给遗传算法添加很多约束,此时便增加了遗传算法寻优的难度。

法二

利用“基于优先级编码”。这种编码方式是Gen & Lin 在2005年提出的有利于很好地求解图的路径规划问题的编码方法。参考文献:(Lin L, Gen M. Priority-Based Genetic Algorithm for Shortest Path Routing Problem in OSPF[M]// Intelligent and Evolutionary Systems. 2009.)这篇文献可能比较难下载到,我将其上传到这里了,可以直接下载查看:

Priority-BasedGeneticAlgorithmforShortestPathRoutingProbleminOSPF_ipospfpriority-机器学习文档类资源-CSDN下载

下面来详细讲解这种编码方式

“基于优先级编码”是一种间接编码方式,这意味着染色体并不能直接表示路径,此时需要利用额外的数据来进行解码,解码后才表示一个路径。这种编码方式有个特点是染色体的每一位上的元素是互不相等的,这意味着这种编码方式具备“排列编码”的特征。(排列编码即比如从1到10的10个数中随机挑选若干个数组成的一个排列。)

以一条染色体为例,看看“基于优先级编码”的染色体是如何表示有向图中的一条路径的:

上面这条染色体是遗传算法中随机生成的(并非最优),结点优先级并不是指结点的访问先后顺序,而是结点的优先级,是给后面解码用的。为什么染色体长度是10?因为此时染色体每一位存储的是图的每个结点的优先级,因此染色体的长度需要和图的结点数一致。当然地,由于上面题目规定了是从结点1开始走,故设染色体长度为9也行。这里为了能和下标更直观地对应,就设染色体长度与图的结点数目一致。

解码需要一个集合nodes,用于存储以各结点为起点的有向边的终点,即各个结点下一步可达的结点。本题的集合nodes为:nodes = [[], [2,3], [3,4,5], [5,6], [7,8], [4,6], [7,9], [8,9], [9,10], [10]]。因为python中列表的下标是从0数起的,而本题的结点是从1数起的,为了能直接对应,故上面的nodes的第0号元素设置为[],表示无意义。解析一下nodes的组成:第1号元素是[2,3],表示题目的有向图中1号结点下一步可达的结点是2和3。nodes的第2号元素是[3,4,5],表示2号结点下一步可达的结点是3,4,5。以此类推。

于是上面的染色体[7, 3, 4, 6, 2, 5, 8, 10, 1, 9]的详细解码过程如下

从1号结点出发,在nodes中下标为1的元素是[2,3],表示1号结点下一步可以去2号结点或3号结点。此时从染色体中找到这两个结点对应的优先级分别为3和4,如图所示:

从中选出具有更高优先级的结点3作为结点1下一步需要访问的结点。

紧接着,在nodes中下标为3的元素是[5,6],表示3号结点下一步可以去5号或6号结点。此时从染色体中找到这两个结点对应的优先级分别为2和5,如图所示:

从中选出具有更高优先级的结点6作为结点1下一步需要访问的结点。

以此类推,最终得到完整的访问路径为:

1 → 3 → 6 → 7 → 8 → 10。

有了解码得到路径,如何求个体的适应度?

想要求个体的适应度,首先要定一个优化目标。本题是要让路径最短,于是我们便要根据访问结点求出路径长度,把这个作为优化目标。有了优化目标,便可利用基于目标函数值排序的适应度分配(ranking)求出适应度值。当然,这类单目标优化问题也可以直接让个体的适应度等于优化目标函数值(即路径长度)。而路径长度即为访问路径上各条有向边的权值之和。

代码实现

首先编写问题类,把待优化模型写在问题类中。然后编写执行脚本,调用“soea_SEGA_templet“(增强精英保留的遗传算法模板)进行进化优化。该算法模板的源码及算法流程详见

geatpy/soea_SEGA_templet.py at master · geatpy-dev/geatpy · GitHubEvolutionary algorithm toolbox and framework with high performance for Python - geatpy/soea_SEGA_templet.py at master · geatpy-dev/geatpyhttps://github.com/geatpy-dev/geatpy/blob/master/geatpy/algorithms/soeas/GA/soea_SEGA_templet.py

由于本题比较简单,故用4个个体、10代的进化即可得到很好的结果。完整的实验代码如下:

# -*- coding: utf-8 -*-
import numpy as np
import geatpy as eaclass MyProblem(ea.Problem): # 继承Problem父类def __init__(self):name = 'Shortest_Path' # 初始化name(函数名称,可以随意设置)M = 1 # 初始化M(目标维数)maxormins = [1] # 初始化maxormins(目标最小最大化标记列表,1:最小化该目标;-1:最大化该目标)Dim = 10 # 初始化Dim(决策变量维数)varTypes = [1] * Dim # 初始化varTypes(决策变量的类型,元素为0表示对应的变量是连续的;1表示是离散的)lb = [0] * Dim # 决策变量下界ub = [9] * Dim # 决策变量上界lbin = [1] * Dim # 决策变量下边界ubin = [1] * Dim # 决策变量上边界# 调用父类构造方法完成实例化ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb, ub, lbin, ubin)# 设置每一个结点下一步可达的结点(结点从1开始数,因此列表nodes的第0号元素设为空列表表示无意义)self.nodes = [[], [2,3], [3,4,5], [5,6], [7,8], [4,6], [7,9], [8,9], [9,10], [10]]# 设置有向图中各条边的权重self.weights = {'(1, 2)':36, '(1, 3)':27, '(2, 4)':18, '(2, 5)':20, '(2, 3)':13, '(3, 5)':12, '(3, 6)':23,'(4, 7)':11, '(4, 8)':32, '(5, 4)':16, '(5, 6)':30, '(6, 7)':12, '(6, 9)':38, '(7, 8)':20,'(7, 9)':32, '(8, 9)':15, '(8, 10)':24, '(9, 10)':13}def decode(self, priority): # 将优先级编码的染色体解码得到一条从节点1到节点10的可行路径edges = [] # 存储边path = [1] # 结点1是路径起点while not path[-1] == 10: # 开始从起点走到终点currentNode = path[-1] # 得到当前所在的结点编号nextNodes = self.nodes[currentNode] # 获取下一步可达的结点组成的列表chooseNode = nextNodes[np.argmax(priority[np.array(nextNodes) - 1])] # 从NextNodes中选择优先级更高的结点作为下一步要访问的结点,因为结点从1数起,而下标从0数起,因此要减去1path.append(chooseNode)edges.append((currentNode, chooseNode))return path, edgesdef aimFunc(self, pop): # 目标函数pop.ObjV = np.zeros((pop.sizes, 1)) # 初始化ObjVfor i in range(pop.sizes): # 遍历种群的每个个体,分别计算各个个体的目标函数值priority = pop.Phen[i, :]path, edges = self.decode(priority) # 将优先级编码的染色体解码得到访问路径及经过的边pathLen = 0for edge in edges:key = str(edge) # 根据路径得到键值,以便根据键值找到路径对应的长度if not key in self.weights:raise RuntimeError("Error in aimFunc: The path is invalid. (当前路径是无效的。)", path)pathLen += self.weights[key] # 将该段路径长度加入pop.ObjV[i] = pathLen # 计算目标函数值,赋值给pop种群对象的ObjV属性## 执行脚本
if __name__ == "__main__":# 实例化问题对象problem = MyProblem()# 构建算法algorithm = ea.soea_EGA_templet(problem,ea.Population(Encoding='RI', NIND=4),MAXGEN=10,  # 最大进化代数logTras=1)  # 表示每隔多少代记录一次日志信息# 求解res = ea.optimize(algorithm, verbose=True, drawing=1, outputMsg=False, drawLog=False, saveFlag=True, dirName='result')print('最短路程为:%s'%(res['ObjV'][0][0]))print('最佳路线为:')best_journey, edges = problem.decode(res['Vars'][0])for i in range(len(best_journey)):print(int(best_journey[i]), end = ' ')print()

运行结果如下:

在有向图中表现为:

后记

值得注意的是:上面题目中的有向图并不存在回路,实际上,复杂的有向图往往会存在许多回路,此时需要进行一定的处理来避免陷入回路当中,即避免一直在回路上“打转”。处理方式有很多,例如在解码过程中对已经访问过的结点的有限度进行惩罚等等。这里暂时就不深入探讨了,待之后讲述无向图最短路径及机器人寻路问题时再展开叙述。

最后回顾一下上一篇博客提到的”遗传算法套路“:

该套路实现了具体问题、使用的算法以及所调用的相关算子之间的脱耦。而Geatpy工具箱已经内置了众多进化算法模板类以及相关的算子,直接调用即可。对于实际问题的求解,只需关心如何把问题写在自定义问题类中就好了。

更多详细的教程可以详见:Geatpy教程 – Geatpy

后续我将继续学习和挖掘该工具箱的更多深入的用法。希望这篇文章在帮助自己记录学习点滴之余,也能帮助大家!


http://chatgpt.dhexx.cn/article/o0u0N2IO.shtml

相关文章

python遗传算法解决分配问题

python遗传算法解决分配问题 import numpy as np import random import matplotlib.pyplot as pltdef get_rand():select [x for x in range(10)]random.shuffle(select)return selecttime np.array([[82, 16, 66, 71, 44, 28, 76, 85, 36, 8],[91, 98, 4, 4, 39, 68, 26, 26…

python遗传算法

学习代码来源于:遗传算法python 一.主要思想 遗传算法是根据达尔文的“适者生存,优胜劣汰”的思想来找到最优解的额,其特点是所找到的解是全局最优解,相对于蚁群算法可能出现的局部最优解还是有优势的。 二.主要名词 个体&…

遗传算法python代码

不知道为什么一个大一的萌新能有这么多事要干......蚁群算法的代码先缓一缓,等博主写完作业,考完英语期中再说吧。关于遗传算法的代码,由于忘记了np数组不copy的时候会直接引用,导致很长一段时间不知道自己哪里出bug了&#xff0c…

python遗传算法解简单整数规划与原理探究

文章目录 一、算例与代码1.1 问题与思路1.2 代码 二、实现细节2.1 什么是种群2.2 编码与解码2.3 如何处理约束2.4 如何从较好解获得新的解 三、反思:真的是采样逼近吗 / 消融实验3.1 最优解和较好解的关系 / 遗传算法为什么可行3.2 为什么交叉能得到更优解3.3 为什么…

遗传算法python

目录 01 遗传本质 02 编码 1.单目标多变量函数 - 无约束 - (可画立体图) 2.单目标多变量函数 - 罚算法 - 约束​编辑 3.单目标多变量函数 - 带约束 (不明白得出了什么东西,可能是输出评价指标?) 4.多目标函数 - 带约束 可…

python遗传算法(应用篇1)--求解一元函数极值

目录 遗传算法求解过程算法参数构建初始化种群解码(二进制>十进制)自然选择交叉变异解码(新种群>十进制)计算新种群的适应度 完整代码及其可视化版本其他numpy中的随机数 下面我们使用遗传算法尝试求解一元函数的最值 y s…

python遗传算法解决分段线性约束问题

问题描述 模型 分析 带有分段约束和max最值,导致使用一般的线性规划pulp问题进行求解会比较麻烦如果将分段约束转化为0/1整数规划,其余变量 u i u_i ui​未必还是整数,就涉及混整问题相对麻烦,所以考虑使用遗传算法进行求解 Py…

Python 遗传算法路径规划

了却一个心愿 文章目录 目录 文章目录 前言 二、主要内容 三、使用步骤 1.将压缩包下载解压 2.读入数据 3.最终结果 前言 遗传算法(Genetic Algorithm,GA)最早是由美国的 John holland于20世纪70年代提出,该算法是根据大自然中生物体进化规律…

遗传算法的python实现(手撕python遗传算法)

遗传算法简介 假设有无约束优化问题: z f ( x , y ) zf(x,y) zf(x,y) 如何通过遗传算法求解? 在这里需要将该优化问题与遗传算法中的概念进行对比。 f f f 对应遗传算法的适应度函数。自变量 x , y x,y x,y 对应遗传算法的个体,注意&#…

Python 遗传算法 Genetic Algorithm

粒子群算法常常用于在连续解空间内搜索,而在不连续、离散的空间内常常会出现搜索越界的问题 例如旅行商问题,寻找可以遍历 15 个地点的最短路径(当然可以用二进制状态压缩 动态规划解决),以 {0, 1, ..., 14} 表示这些…

python遗传算法之geatpy学习

😻今天我们来学习python中的遗传算法的使用,我们这里使用的是geatpy的包进行学习,本博客主要从geatpy中的各种数据结构一步一步进行学习,请大家耐心看完。 🐤其实以前也学习过遗传算法,但是主要使用matlab…

遗传算法详解 附python代码实现

遗传算法 遗传算法是用于解决最优化问题的一种搜索算法。从名字来看,遗传算法借用了生物学里达尔文的进化理论:”适者生存,不适者淘汰“,将该理论以算法的形式表现出来就是遗传算法的过程。 问题引入 上面提到遗传算法是用来解…

遗传算法【Python】

遗传算法概念 基本思想: 遗传算法(GA)是一种全局寻优搜索算法,它依据的是大自然生物进化过程中“适者生存”的规律。它首先对问题的可行解进行编码,组成染色体,然后通过模拟自然界的进化过程,对初始种群中的染色体进…

遗传算法python实现

遗传算法python实现 一、问题引入二、遗传算法的步骤1.初始化2.个体评价3.选择运算4.交叉运算5.变异运算6.终止条件判断 三、实现思路1.编码的设计2.适应度函数3.选择函数4.交叉函数5.变异函数6.迭代 四、具体实现1.编码解码函数2.适应度函数3.选择函数4.交叉函数5.变异函数6.选…

遗传算法(Python)

一、遗传算法 1、遗传算法的定义 遗传算法是一种现代优化算法。根据自然界适者生存的法则,种群中优秀个体的基因进行遗传。每个个体的染色体通过选择、交叉和变异等过程产生新的适应度更大的染色体,其中适应度越大的个体越优秀,种群得到优化…

python遗传算法(详解)

学习代码来源于:遗传算法python 一.主要思想 遗传算法是根据达尔文的“适者生存,优胜劣汰”的思想来找到最优解的额,其特点是所找到的解是全局最优解,相对于蚁群算法可能出现的局部最优解还是有优势的。 二.主要名词 个体&…

DH算法、DHE算法、ECDHE算法演进

ECDHE 算法解决了 RSA 算法不具备前向安全的性质 和 DH 算法效率低下的问题。 ECDHE 算法具有前向安全。所以被广泛使用。 由什么演变而来 DH 算法 -- > DHE 算法 -- > ECDHE 算法 DH 算法是非对称加密算法,该算法的核心数学思想是离散对数。 核心数学思…

DH 算法思想 SSH解决内容篡改问题

DH算法用于交换密钥 交换密钥的目的是生成仅双方共享的密钥 交换密钥的基本过程: 双方确定公开的内容用各自的私钥分别对公共内容加密(加密本质就是数学运算)并发送给对方这时双方使用自己的密钥对收到的内容加密(要设计运算保证…

ECDH算法详解

ECDH算法详解 ECDH算法详解DH密钥交换原理结合ECC椭圆曲线算法ECDSA签名算法 参考资料 ECDH算法详解 DH密钥交换原理 进一步解释: 两端(Alice 和 Bob)想要安全的交换信息并且第三方不能获取到该信息。当然这也是TLS协议中的目的之一&#xf…

DH算法(密钥交换算法)

一 对称加密缺点 密钥传递过程复杂,这是对称加密带来的困扰。 二 DH密钥交换算法特点 构建本地密钥 双方密钥一致 三 DH相关参数 四 DH算法实现过程 1、初始化发送方的密钥(KeyPairGenerator、KeyPair、PublicKey) 2、初始化接受方的密钥&…