【路径规划】(1) Dijkstra 算法求解最短路,附python完整代码

news/2024/5/18 16:24:04/文章来源:https://blog.csdn.net/dgvv4/article/details/128055792

好久不见,我又回来了,这段时间把路径规划的一系列算法整理一下,感兴趣的点个关注。今天介绍一下机器人路径规划算法中最基础的 Dijkstra 算法,文末有 python 完整代码,那我们开始吧。


1. 算法介绍

1959 年,荷兰计算机科学家 ·EdsgerWybe·Dijkstra 发表了论文《 A note on two problems in connexion with graphs 》,提出了 Dijkstra 算法。发展至今日,Dijkstra 算法成为了解决带权图最短路径问题的经典算法之一,现在常常被用于网络内部路由问题的求解或者作为其它的复杂图论算法的子算法辅助进行计算。 

近年来,Dijkstra 算法在许多领域得到广泛应用,比如:物流中心分层选址[1]、电网故障行波定位[2]、电能路由策略[3]。众多学者作了研究,比如:徐洋洋等人提出将Dijkstra 算法应用于交通阻塞路径规划中,生成的最佳路径有效避开拥堵路段[4];吴红波等人提出 Dijkstra 算法和 GIS 网络分析的有效集成不但可以实现城市车辆行驶路线优化决策,而且 Dijkstra 算法优化能减少节点访问次数和时间复杂度[5];王芝麟等人提出使用最小二叉堆作为 Dijkstra 最短路径算法的辅助数据结构,有效降低算法的运算次数并提高运算效率[6]。

参考文献:

[1] 靳国伟,何世伟,黎浩东,何必胜,殷玮川.Harmony Search-Dijkstra 混合算法在铁路物流中心分层选址中的应用[J].北京交通大学学报,2016,40(04):45-52.

[2] 李泽文,唐平,曾祥君,肖仁平,赵廷.基于 Dijkstra 算法的电网故障行波定位方法[J].电力系统自动化,2018,42(18):162-168.

[3] 江渝,叶泓炜,张青松,王克,徐志鹏,杨睿.能源互联网中基于 Dijkstra 算法的分布式电能路由策略的实现[J].电网技术,2017,41(07).

[4] 吴红波,王英杰,杨肖肖.基于 Dijkstra 算法优化的城市交通路径分析[J].北京交通大学学报,2019,43(04):116-121+130.

[5] 王芝麟,乔新辉,马旭,严研.一种基于二叉堆的Dijkstra 最短路径优化方法[J].工程数学学报,2021,38(05):709-720.


2. 算法原理

Dijkstra 算法是典型的单源最短路径计算算法用于解决源点到其它所有点之间的最短路径计算的问题。它采用了贪心的思想搜索全局,求取最优解,搜索过程是以起点为圆心,向周围以同心圆的方式进行无序扩张搜索,搜索完全部节点后算法才终止经典 Dijkstra 算法的搜索过程如下图所示,最外层的实线圆代表所有待搜索的点的集合。 

下图展示了 Dijkstra 算法的一般运算流程,为了更直观的描述运算过程,下面以图论的方法来描述 Dijkstra 算法:设 G=(V,E) 是一个带权有向图。其中 V 表示图中所有顶点的集合E 表示图中每条边的长度权值

将顶点集合 V 分为两组,第一组为已找到最短路径的顶点集合,用 Close 表示,初始 Close 集合中只包含有源节点,每求得一条最短路径,  就将对应的中间结点加入到集合 Close 中

第二组为其余未确定最短路径的顶点集合,用 OPEN 表示。按最短路径的递增次序依次把第二组中的顶点加入 Close 中。

此外,每个顶点都对应着一个距离,Close 中的顶点的距离就是从 v 到此顶点的最短路径长度OPEN 中的顶点的距离是从 v 到此顶点只包括 S 中的顶点为中间顶点的当前路径的最短长度。其中节点到自身的距离视为 0。

算法步骤如下:

Step1:初始时,生成集合 Close={v},集合 OPEN={其余顶点},集合 Close 和 OPEN 互补;

Step2:从集合 OPEN 中选取一个距离 v 最小的顶点 k,把 k 加入集合 Close 中(该距离就是 v 到 k 的最短路径),记录节点 v 为节点 k 的父节点

Step3:以 k 为新考虑的中间点,修改 OPEN 集合中各顶点的距离:若从源点 v 到顶点 u 的距离比原来距离短,则修改顶点 u 的距离值,修改后的距离值为顶点 k 的距离加上边上的权,同时修改节点 k 的父节点;

Step4:重复 Step2 和 Step3 直到所有顶点都包含在集合 Close 中;

Step5:根据目标节点的父节点反向进行迭代,输出最短路径。 


接下来将以下图所示的井下巷道的局部点网图为例,使用 Dijkstra 算法进行最优路径规划并列表进行算法的运算步骤说明,图中距离单位均为千米。

A 点为矿井副井口,即逃生终点H 点为井下被困人员的当前位置,即逃生起点,搜索过程从 H 点开始,逐渐向外开始搜索,一直到搜索完所有节点才停止。

初始时,Close 表中仅包含起点 H,OPEN 表中包含有其余所有的节点。从 F 点开始进行搜索。下表中列出了基于 Dijkstra 算法的最短疏散路径求解过程。 

从求解过程中可以看到,不管需要求取最短路径的是哪两个点,Dijkstra 算法总会求出从源节点到图 G 中所有顶点的最短路径。反映到算法的计算过程,就是将集合 S 从仅含有源节点的一个集合逐步变成为全集,U 集合变为空集求取完成之后,再根据父节点进行推演得出所需求解的最短路径。 


3. 代码实现

算法优点:鉴于 Dijkstra 算法的全局遍历性,其计算结果准确性非常高,Dijkstra 算法可以避开局部最优陷阱,100%的求解出最优路径

算法缺点:但是正由于其要求遍历所有节点,在路径节点比较多的时候,计算速度会大大降低。由于 Dijkstra 算法使用了两次循环,所以它的时间复杂度为 o(n^2),其中 n 为图中的顶点个数。在顶点数较多的情况下,算法的运算效率将受到影响,

3.1 伪代码

算法的一般求解流程用伪代码表示如下: 


3.2 python 代码

实现效果图如下,左图为搜索过程,右图为最终路径

 老样子每句都有注释,有问题可以在评论区留言

import math
import matplotlib.pyplot as plt
min_set = 10
show_animation = True  # 绘图# 创建一个类
class Dijkstra:# 初始化def __init__(self, ox, oy, resolution, robot_radius):# 属性分配self.min_x = Noneself.min_y = Noneself.max_x = Noneself.max_y = Noneself.x_width = Noneself.y_width = Noneself.obstacle_map = Noneself.resolution = resolution  # 网格大小(m)self.robot_radius = robot_radius  # self.calc_obstacle_map(ox, oy)  # 绘制栅格地图self.motion = self.get_motion_model()  # 机器人运动方式# 构建节点,每个网格代表一个节点class Node:def __init__(self, x, y, cost, parent_index):self.x = x  # 网格索引self.y = yself.cost = cost  # 路径值self.parent_index = parent_index  # 该网格的父节点def __str__(self):return str(self.x) + ',' + str(self.y) + ',' + str(self.cost) + ',' + str(self.parent_index)# 寻找最优路径,网格起始坐标(sx,sy),终点坐标(gx,gy)def planning(self, sx, sy, gx, gy):# 节点初始化# 将已知的起点和终点坐标形式转化为节点类型,0代表路径权重,-1代表无父节点start_node = self.Node(self.calc_xy_index(sx, self.min_x),self.calc_xy_index(sy, self.min_y), 0.0, -1)# 终点goal_node = self.Node(self.calc_xy_index(gx, self.min_x),self.calc_xy_index(gy, self.min_y), 0.0, -1)# 保存入库节点和待计算节点open_set, closed_set = dict(), dict()# 先将起点入库,获取每个网格对应的keyopen_set[self.calc_index(start_node)] = start_node# 循环while 1:# 获取外库中损失最小的节点索引c_id, 获取该节点c_id = min(open_set, key=lambda o: open_set[o].cost)current = open_set[c_id]  # 从字典中取出该节点# 绘图if show_animation:# 网格索引转换为真实坐标plt.plot(self.calc_position(current.x, self.min_x),self.calc_position(current.y, self.min_y), 'xc')plt.pause(0.001)# 判断是否是终点,如果选出来的损失最小的点是终点if current.x == goal_node.x and current.y == goal_node.y:# 更新终点的父节点goal_node.cost = current.cost# 更新终点的损失goal_node.parent_index = current.parent_indexbreak# 在外库中删除该最小损失点,把它入库del open_set[c_id]closed_set[c_id] = current# 遍历邻接节点for move_x, move_y, move_cost in self.motion:# 获取每个邻接节点的节点坐标,到起点的距离,父节点node = self.Node(current.x + move_x,current.y + move_y, current.cost + move_cost, c_id)# 获取该邻居节点的keyn_id = self.calc_index(node)# 如果该节点入库了,就看下一个if n_id in closed_set:continue# 邻居节点是否超出范围了,是否在障碍物上if not self.verify_node(node):continue# 如果该节点不在外库中,就作为一个新节点加入到外库if n_id not in open_set:open_set[n_id] = node# 节点在外库中时else:# 如果该点到起点的距离,要小于外库中该点的距离,就更新外库中的该点信息,更改路径if node.cost <= open_set[n_id].cost:open_set[n_id] = node# 找到终点rx, ry = self.calc_final_path(goal_node, closed_set)return rx, ry# 机器人行走的方式,每次能向周围移动8个网格移动@staticmethoddef get_motion_model():# [dx, dy, cost]motion = [[1,0,1],  # 右[0,1,1],  # 上[-1,0,1], # 左[0,-1,1], # 下[-1,-1,math.sqrt(2)], # 左下[-1,1,math.sqrt(2)], # 左上[1,-1,math.sqrt(2)], # 右下[1,1,math.sqrt(2)]]  # 右上return motion# 绘制栅格地图def calc_obstacle_map(self, ox, oy):# 地图边界坐标self.min_x = round(min(ox))  # 四舍五入取整self.min_y = round(min(oy)) self.max_x = round(max(ox))self.max_y = round(max(oy))# 地图的x和y方向的栅格个数,长度/每个网格的长度=网格个数self.x_width = round((self.max_x-self.min_x)/self.resolution)  # x方向网格个数self.y_width = round((self.max_y-self.min_y)/self.resolution)  # y方向网格个数# 初始化地图,二维列表,每个网格的值为Falseself.obstacle_map = [[False for _ in range(self.y_width)]for _ in range(self.x_width)]# 设置障碍物for ix in range(self.x_width):  # 遍历x方向的网格 [0:x_width]x = self.calc_position(ix, self.min_x)   # 根据网格索引计算x坐标位置for iy in range(self.y_width):  # 遍历y方向的网格 [0:y_width]y = self.calc_position(iy, self.min_y)  # 根据网格索引计算y坐标位置# 遍历障碍物坐标(实际坐标)for iox, ioy in zip(ox, oy):# 计算障碍物和网格点之间的距离d = math.hypot(iox-x, ioy-y)# 膨胀障碍物,如果障碍物和网格之间的距离小,机器人无法通行,对障碍物膨胀if d <= self.robot_radius:# 将障碍物所在网格设置为Trueself.obstacle_map[ix][iy] = Truebreak  # 每个障碍物膨胀一次就足够了# 根据网格编号计算实际坐标def calc_position(self, index, minp):# minp代表起点坐标,左下x或左下ypos = minp + index * self.resolution  # 网格点左下左下坐标return pos# 位置坐标转为网格坐标def calc_xy_index(self, position, minp):# (目标位置坐标-起点坐标)/一个网格的长度==>目标位置的网格索引return round((position-minp) / self.resolution)# 给每个网格编号,得到每个网格的keydef calc_index(self, node):# 从左到右增大,从下到上增大return node.y * self.x_width + node.x# 邻居节点是否超出范围def verify_node(self, node):# 根据网格坐标计算实际坐标px = self.calc_position(node.x, self.min_x)py = self.calc_position(node.y, self.min_y)# 判断是否超出边界if px < self.min_x:return Falseif py < self.min_y:return Falseif px >= self.max_x:return Falseif py >= self.max_y:return False# 节点是否在障碍物上,障碍物标记为Trueif self.obstacle_map[node.x][node.y]:return False# 没超过就返回Truereturn True# 计算路径, parent属性记录每个节点的父节点def calc_final_path(self, goal_node, closed_set):# 先存放终点坐标(真实坐标)rx = [self.calc_position(goal_node.x, self.min_x)]ry = [self.calc_position(goal_node.y, self.min_y)]# 获取终点的父节点索引parent_index = goal_node.parent_index# 起点的父节点==-1 while parent_index != -1:n = closed_set[parent_index]  # 在入库中选择父节点rx.append(self.calc_position(n.x, self.min_x))  # 节点的x坐标ry.append(self.calc_position(n.y, self.min_y))  # 节点的y坐标parent_index = n.parent_index  # 节点的父节点索引return rx, rydef main():# 设置起点和终点sx = -5.0sy = -5.0gx = 50.0gy = 50.0# 网格大小grid_size = 2.0# 机器人半径robot_radius = 1.0 # 设置障碍物位置ox, oy = [], []for i in range(-10,60):    ox.append(i); oy.append(-10.0)  # 下边界for i in range(-10,60):    ox.append(60.0); oy.append(i)  # 右边界for i in range(-10,61):    ox.append(i); oy.append(60.0)  # 上边界for i in range(-10,61):    ox.append(-10.0); oy.append(i)  # 左边界for i in range(-10,40):    ox.append(20.0); oy.append(i)  # 左围栏for i in range(0,40):      ox.append(40.0); oy.append(60-i)  # 右围栏# 绘图if show_animation:plt.plot(ox, oy, '.k')  # 障碍物黑色plt.plot(sx, sy, 'og')  # 起点绿色plt.plot(gx, gy, 'xb')  # 终点蓝色plt.grid(True)plt.axis('equal')  # 坐标轴刻度间距等长# 实例化,传入障碍物,网格大小dijkstra = Dijkstra(ox, oy, grid_size, robot_radius)# 求解路径,返回路径的 x 坐标和 y 坐标列表rx, ry = dijkstra.planning(sx, sy, gx, gy)# 绘制路径经过的网格if show_animation:plt.plot(rx, ry, '-r')plt.pause(0.01)plt.show()if __name__ == '__main__':main()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_226068.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FAIRNESS IN MACHINE LEARNING: A SURVEY 阅读笔记

论文链接 刚读完一篇关于机器学习领域研究公平性的综述&#xff0c;这篇综述想必与其有许多共通之处&#xff0c;重合部分不再整理笔记&#xff0c;可详见上一篇论文的笔记&#xff1a; A Survey on Bias and Fairness in Machine Learning 阅读笔记_Catherine_he_ye的博客 S…

scrapy的入门使用

目录 一、 安装scrapy 1.windonws/Mac安装命令&#xff1a; 2. 安装依赖包&#xff1a;pip install pypiwin32 二、 scrapy项目开发流程 1.创建项目:    2.生成一个爬虫: 3.提取数据: 4.保存数据: 三、 创建项目 四、创建爬虫 五、完善爬虫 5.2 定位元素以及提取…

浅识vue的虚拟DOM和渲染器

虚拟DOM本质上是对DOM的抽象描述&#xff0c;就是一个普通的js对象。他身上的属性要比真实DOM的属性要少得多。 在一定情况下&#xff0c;使用虚拟DOM的性能要逊于直接使用真实DOM。 例如&#xff0c;在页面一开始的时候&#xff0c;Vue需要先通过生成虚拟DOM树&#xff0c;在…

【面试】揭秘面试背后的那点真实

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录前言/背景面试流程资料总结/刷题指南个人经验总结寄语&#x1f338;I could be bounded in a nutshell and count myself a king of infinite space. 特别鸣谢&#xff1a;木芯工作室 、Ivan from Russia 金…

QT:debug日志—打不开头文件以及qDebug和Q_CLASSINFO的使用

这个是因为链接器在给定路径上搜索不到对应的头文件&#xff0c;而大多数的Qt相关的头文件都集中在一个include文件夹里&#xff1a; 我电脑上的路径是&#xff1a;C:\Qt\Qt5.9.7\5.9.7\msvc2017_64\include 然后我们在项目设置里&#xff1a; 注意&#xff0c;这边要加上\*&…

计算机系统基础期末复习

C语言代码如下&#xff1a; void fun(int n){ int x n*12;int y n/32; }请将其中计算的部分优化为位运算、移位运算和加法运算的结合。 x n8n4 (n<<3)(n<<2) x (n(n>>31) & 0x1F)>>5 设32位的位串为x(x类型为unsigned int)&#xff0c;现要…

Flink常用Sink(elasticsearch(es)Sink、RedisSink、KafkaSink、MysqlSink、FileSink)

flink输出到es、redis、mysql、kafka、file 文章目录配置pom文件公共实体类KafkaSInkElasticsearchSink(EsSink)RedisSinkMysqlSink(JdbcSink)FileSink自己先准备一下相关环境 配置pom文件 <properties><maven.compiler.source>8</maven.compiler.source>&l…

测试用例设计方法之场景设计法

基本流&#xff1a;采用直黑线表示&#xff0c;是经过用例的最简单的路径&#xff08;无任何差错&#xff0c;程序从开始直接执行到结束&#xff09; 备选流&#xff1a;采用不同颜色表示&#xff0c;一个备选流可能从基本流开始&#xff0c;在某个特定条件下执行&#xff0c;…

HTTP介绍报文格式构造

HTTP 一. 简单介绍一下: 二. 学习报文格式: 三. HTTP中的细节介绍 四, 如何构造一个HTTP请求 一. 简单介绍一下: 是应用层的典型协议客户端发送一个HTTP请求, 服务器返回一个HTTP响应(一问(请求)一答(响应)的)HTTP是文本格式的协议二. 学习报文格式: 1)先简单看一看HTTP的…

在CentOS 7.7 x86_64上为python 2.7.5安装pip的靠谱方法

我的虚拟机是CentOS 7.7 x86_64系统&#xff0c;对应的python默认版本是2.7.5&#xff0c;但是没有安装pip&#xff0c;不方便安装第三方模块。 我想为为它安装pip工具&#xff0c;发现现有的安装方法都行不通了&#xff0c;比如先安装easy_install&#xff0c;再通过easy_inst…

Nginx (4):nginx动静分离

什么是动静分离不解释了&#xff0c;网上说的很清楚&#xff0c;这里只说配置 目的 02虚拟机运行一个tomcat&#xff0c;处理动态请求&#xff0c;而对静态文件的访问则交给01虚拟机。操作 下面是01虚拟机的配置文件内容&#xff1a; server {listen 82;listen [::]:82;#root /…

pytorch案例代码-3

双向循环神经网络 双向循环神经网络在RNN/LSTM/GRU里都有。比如RNN cell&#xff0c;只是把h0和x1传入做线性变换产生h1继续传入同一个cell做线性变换&#xff0c;线性变换的W和b共享&#xff0c;沿着这个方向就把所有隐层和最后的输出算出来了。 那么其中的每个结点&#xff0…

文华财经期货K线多周期画线技术,多重短线技术共振通道线指标公式——多周期主图自动画线

期货指标公式是通过数学逻辑角度计算而来&#xff0c;仅是期货分析环节中的一个辅助工具。期货市场具有不确定性和不可预测性的&#xff0c;请正常对待和使用指标公式! 期货指标公式信号本身就有滞后性&#xff0c;周期越大&#xff0c;滞后性越久。指标公式不是100%稳赚的工具…

18.4 嵌入式指针概念及范例、内存池改进版

一&#xff1a;嵌入式指针&#xff08;embedded pointer&#xff09; 1、嵌入式指针概念 一般应用在内存池相关的代码中&#xff0c;成功使用嵌入式指针有个前提条件&#xff1a;&#xff08;类A对象的sizeof必须不小于4字节&#xff09; 嵌入式指针工作原理&#xff1a;借用…

Word2Vec 实践

Word2Vec 实践 gensim库使用 这里的Word2Vec借助 gensim 库实现&#xff0c;首先安装pip install gensim3.8.3 from gensim.models.word2vec import Word2Vecmodel Word2Vec(sentencesNone, size100, alpha0.025, window5, min_count5,max_vocab_sizeNone, sample1e-3, …

2023年系统规划与设计管理师-第三章信息技术服务知识

一. 思维导图 二.IT 服务管理 (ITSM) 1. 什么是 IT 服务管理 (ITSM)&#xff1f; IT 服务管理 (ITSM) 包含一组策略和实践&#xff0c;这些策略和实践可用于为最终用户实施、交付和管理 IT 服务&#xff0c;以满足最终用户的既定需求和企业的既定目标。 在此定义中&#xff0…

cocos2dx创建工程并在androidstudio平台编译

本文主要是通过androidstudio进行编译运行cocos2dx工程。 前置条件&#xff1a; 1&#xff1a;androidstudio已经下载并安装。 2&#xff1a;cocos2dx已经下载并打开。 这里androidstudio使用2021.3.1版本&#xff0c;cocos2dx使用4.0版本。 第一步&#xff0c;首先安装py…

基于JavaWeb的药品进销存管理系统(JSP)

目 录 绪论 1 1.1 本课题的研究背景 1 1.2 国内外研究现状 1 1.3 本课题的主要工作 2 1.4 目的和意义 2 开发工具及技术 3 2.1 开发工具 3 2.1.1 MyEclipse 3 2.1.2 Tomcat 3 2.1.3 Mysql 3 2.2 开发技术 4 2.2.1 JSP 4 2.2.2 MyBatis 4 2.2.3 JavaScript 4 2.2.4 jQuery以及j…

六、nacos环境隔离、服务配置拉取和多环境配置共享

文章目录一、环境隔离-namespace1.namespace理解2.创建命名空间二、Nacos-实现配置管理三、nacos-实现服务配置拉取1.非热更新2.热更新&#xff1a;四、实现多环境配置共享1.开发环境&#xff1a;2.测试环境3.结论一、环境隔离-namespace 1.namespace理解 Nacos中服务存储和数…

Servlet到底是什么(非常透彻)

Servlet到底是什么&#xff1f;1. Servlet的概念2. Servlet是一种规范3. Servlet的接口4. JSP是什么学习顺序1. Servlet的概念 Servlet 是 Server Applet 的缩写&#xff0c;译为“服务器端小程序”&#xff0c;是一种使用 Java 语言来开发动态网站的技术。 Servlet 虽然被称…