python3实现Dijkstra算法最短路径的实现


Posted in Python onMay 12, 2021

问题描述

现有一个有向赋权图。如下图所示:

python3实现Dijkstra算法最短路径的实现

问题:根据每条边的权值,求出从起点s到其他每个顶点的最短路径和最短路径的长度。
说明:不考虑权值为负的情况,否则会出现负值圈问题。
s:起点
v:算法当前分析处理的顶点
w:与v邻接的顶点
d v d_v dv​:从s到v的距离
d w d_w dw​:从s到w的距离
c v , w c_{v,w} cv,w​:顶点v到顶点w的边的权值

问题分析

Dijkstra算法按阶段进行,同无权最短路径算法(先对距离为0的顶点处理,再对距离为1的顶点处理,以此类推)一样,都是先找距离最小的。
在每个阶段,Dijkstra算法选择一个顶点v,它在所有unknown顶点中具有最小的 d v d_v dv​,同时算法声明从s到v的最短路径是known的。阶段的其余部分为,对w的 d v d_v dv​(距离)和 p v p_v pv​(上一个顶点)更新工作(当然也可能不更新)。
在算法的每个阶段,都是这样处理的:
【0.5】在无权的情况下,若 d w d_w dw​= ∞ \infty ∞则置 d w = d v + 1 d_w=d_v+1 dw​=dv​+1(无权最短路径)
【1】在有权的情况下,若 d w d_w dw​= ∞ \infty ∞则置 d w = d v + c v , w d_w=d_v+c_{v,w} dw​=dv​+cv,w​
【2】若 d w d_w dw​!= ∞ \infty ∞,开始分析:从顶点v到顶点w的路径,若能使得w的路径长比w原来的路径长短一点,那么就需要对w进行更新,否则不对w更新。即满足 d v + c v , w &lt; d w d_v+c_{v,w}&lt;d_w dv​+cv,w​<dw​时,就需要把 d w d_w dw​的值更新为 d v + c v , w d_v+c_{v,w} dv​+cv,w​,同时顶点w的 p v p_v pv​值得改成顶点v

实现过程

考虑Dijkstra算法过程中,有一个数据变化表。

python3实现Dijkstra算法最短路径的实现

python3实现Dijkstra算法最短路径的实现

初始状态如上。开始顶点s是 v 1 v_1 v1​,所有顶点都是unknown的。 v 1 v_1 v1​的 d v d_v dv​的值为0,因为它是起点。

【1】选择unknown顶点中, d v d_v dv​值最小的顶点,即顶点 v 1 v_1 v1​。首先将 v 1 v_1 v1​标记为known。对与 v 1 v_1 v1​邻接的顶点 v 2 v 4 v_2 v_4 v2​v4​进行调整: v 2 v_2 v2​的距离变为 d v + c v , w d_v+c_{v,w} dv​+cv,w​即 v 1 v_1 v1​的 d v d_v dv​值+ c 1 , 2 c_{1,2} c1,2​即0+2=2, v 2 v_2 v2​的 p v p_v pv​值变为 v 1 v_1 v1​;同理,对 v 4 v_4 v4​作相应的处理。

python3实现Dijkstra算法最短路径的实现

【2】选择unknown顶点中, d v d_v dv​值最小的顶点,即顶点 v 4 v_4 v4​(其距离为1,最小)。将 v 4 v_4 v4​标记为known。对其邻接的顶点 v 3 v 5 v 6 v 7 v_3 v_5 v_6 v_7 v3​v5​v6​v7​作相应的处理。

python3实现Dijkstra算法最短路径的实现

【3】选择unknown顶点中, d v d_v dv​值最小的顶点,即顶点 v 2 v_2 v2​(其距离为2,最小)。将 v 2 v_2 v2​标记为known。对其邻接的顶点 v 4 v 5 v_4v_5 v4​v5​作相应的处理。但 v 4 v_4 v4​是已知的,所以不需要调整;因为经过 v 2 v_2 v2​到 v 5 v_5 v5​的距离为2+10=12,而s到 v 5 v_5 v5​路径为3是已知的(上表中, v 5 v_5 v5​的 d v d_v dv​为3),12>3,所以也不需要也没有必要调整。

python3实现Dijkstra算法最短路径的实现

【4】选择unknown顶点中, d v d_v dv​值最小的顶点,即顶点 v 5 v_5 v5​(距离为3,最小,其实还有 v 3 v_3 v3​也是距离为3,但博主发现这里,先 v 5 v_5 v5​再 v 3 v_3 v3​和先 v 3 v_3 v3​再 v 5 v_5 v5​的运行结果都是一样的)。将 v 5 v_5 v5​标记为known。对其邻接的顶点 v 7 v_7 v7​作相应的处理。但原路径长更小,所以不用调整。

【5】再对 v 3 v_3 v3​处理。对 v 6 v_6 v6​的距离下调到3+5=8

python3实现Dijkstra算法最短路径的实现

【6】再对 v 7 v_7 v7​处理。对 v 6 v_6 v6​的距离下调到5+1=6

python3实现Dijkstra算法最短路径的实现

【7】最后,再对 v 6 v_6 v6​处理。不需调整。

python3实现Dijkstra算法最短路径的实现

上述实现过程对应的算法,可能需要用到优先队列,每次出队 d v d_v dv​值最小的顶点,因为如果只是遍历来找到 d v d_v dv​值最小的顶点,可能会花费很多时间。

如何使用数据变化表

数据变化表的最终情况如下:

python3实现Dijkstra算法最短路径的实现

现在我们能找到起点 v 1 v_1 v1​到任意的 v i v_i vi​(除了起点)的最短路径,及其最短路径长。
比如,找到 v 1 v_1 v1​到 v 3 v_3 v3​的最短路径。
【1】 v 3 v_3 v3​的 d v d_v dv​值为3,所以最短路径长为3
【2】 v 3 v_3 v3​的 p v p_v pv​值为 v 4 v_4 v4​,所以 v 3 v_3 v3​的上一个顶点为 v 4 v_4 v4​
【3】到代表 v 4 v_4 v4​的第四行,发现 v 4 v_4 v4​的 p v p_v pv​值为 v 1 v_1 v1​,所以 v 4 v_4 v4​的上一个顶点为 v 1 v_1 v1​
【4】 v 1 v_1 v1​是起点,结束。 v 3 v_3 v3​上一个是 v 4 v_4 v4​, v 4 v_4 v4​上一个是 v 1 v_1 v1​,反过来就得到了最短路径 v 1 = &gt; v 4 = &gt; v 3 v_1=&gt;v_4=&gt;v_3 v1​=>v4​=>v3​
上述分析,其实就是求最短路径的算法的思想:在对每个顶点对象进行处理后变成数据变化表的最终情况后,可以通过对任意顶点 v i v_i vi​的 p v p_v pv​值,回溯得到反转的最短路径。

代码实现

纸上得来终觉浅,绝知此事要躬行!使用python3来实现功能。
本文提到,将使用优先队列来实现寻找未知顶点中,具有最小dist的顶点。使用python已有实现好的优先队列。但实验中报错如下:

python3实现Dijkstra算法最短路径的实现

意思,Vertex实例并不支持小于比较运算符。所以需要实现Vertex类的__lt__方法。下面科普一下:

 

方法名 比较运算符 含义
__eq__ == equal
__lt__ < less than
__le__ <= less and equal
__gt__ > greater than
__ge__ >= greater and equal

但很遗憾,python库自带的优先队列from queue import PriorityQueue,并不满足本文的需求。当PriorityQueue的元素为对象时,需要该对象的class实现__lt__函数,在往优先队列里添加元素时,内部是用的堆排序,堆排序的特点为每个堆(以及每个子堆)的第一个元素总是那个最小的元素。关键在于,在建立了这个堆后,堆就已经记录下来了创建堆时各个元素的大小关系了,在创建优先队列后,再改变某个对象的值,这个堆的结构是肯定不会变的,所以这种堆排序造成了排序是一次性的,如果之后某个对象的属性发生变化,堆的结构也不会随之而改变。

或者说,我们想要的优先队列肯定不是系统提供的优先队列,因为我们要支持可变对象的成员修改导致堆的改变,解决方案有三种,1.内部使用的堆排序的堆,最起码要支持,删除任意节点和增加节点操作(因为这两步就可以达到修改的效果了)2.这个内部堆,在执行出队操作时,考察哪个节点有修改操作,再把堆改变到正确的形态,再出队3.维护一个list,进行排降序,然后每改变一个可变对象的值,就对这个对象进行冒泡或者二分查找找到位置(因为别的都是已经排好序的了,只有它不在正确的位置),最后再list.pop(),但第三个方案是我后来想到的,所以下面代码并不是这样实现的,读者可以进行尝试,肯定比每次遍历全部快。

应该说,可能用不上队列了。我们可能只需要一个list或者set来存储v,在出队前随便vi改变其dist,在出队时再遍历找到最小的dist的vi,再删除掉这个vi即可。因为vi的dist一直在变,需求特殊,但是没必要专门造个轮子(感觉这个轮子也不好造),虽然时间复杂度可能高了点,但代码简单了啊。

优先队列中的堆排序

失效代码如下:三个节点对象的dist都是无穷大,在三个对象都进入队列,再把v3的dist改成0,想要的效果是出队出v3,但出队出的是v1。原因如上:

from queue import PriorityQueue
class Vertex:
    #顶点类
    def __init__(self,vid,dist):
        self.vid = vid
        self.dist = dist
    
    def __lt__(self,other):
        return self.dist < other.dist   
v1=Vertex(1,float('inf'))
v2=Vertex(2,float('inf'))
v3=Vertex(3,float('inf'))

vlist = [v1,v2,v3]
q = PriorityQueue()

for i in range(0,len(vlist)):
    q.put(vlist[i])
v3.dist = 0

print('vid:',q.get().vid)#结果为vid: 1

而如果将在入队前,就把dist改变了,就能正确的出队。

v3.dist = 0
for i in range(0,len(vlist)):
    q.put(vlist[i])
#结果为vid: 3

使用set代替优先队列

class Vertex:
    #顶点类
    def __init__(self,vid,outList):
        self.vid = vid#出边
        self.outList = outList#出边指向的顶点id的列表,也可以理解为邻接表
        self.know = False#默认为假
        self.dist = float('inf')#s到该点的距离,默认为无穷大
        self.prev = 0#上一个顶点的id,默认为0
    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.vid == other.vid
        else:
            return False
    def __hash__(self):
        return hash(self.vid)

#创建顶点对象
v1=Vertex(1,[2,4])
v2=Vertex(2,[4,5])
v3=Vertex(3,[1,6])
v4=Vertex(4,[3,5,6,7])
v5=Vertex(5,[7])
v6=Vertex(6,[])
v7=Vertex(7,[6])
#存储边的权值
edges = dict()
def add_edge(front,back,value):
    edges[(front,back)]=value
add_edge(1,2,2)
add_edge(1,4,1)
add_edge(3,1,4)
add_edge(4,3,2)
add_edge(2,4,3)
add_edge(2,5,10)
add_edge(4,5,2)
add_edge(3,6,5)
add_edge(4,6,8)
add_edge(4,7,4)
add_edge(7,6,1)
add_edge(5,7,6)
#创建一个长度为8的数组,来存储顶点,0索引元素不存
vlist = [False,v1,v2,v3,v4,v5,v6,v7]
#使用set代替优先队列,选择set主要是因为set有方便的remove方法
vset = set([v1,v2,v3,v4,v5,v6,v7])

def get_unknown_min():#此函数则代替优先队列的出队操作
    the_min = 0
    the_index = 0
    j = 0
    for i in range(1,len(vlist)):
        if(vlist[i].know is True):
            continue
        else:
            if(j==0):
                the_min = vlist[i].dist
                the_index = i
            else:
                if(vlist[i].dist < the_min):
                    the_min = vlist[i].dist
                    the_index = i                    
            j += 1
    #此时已经找到了未知的最小的元素是谁
    vset.remove(vlist[the_index])#相当于执行出队操作
    return vlist[the_index]

def main():
    #将v1设为顶点
    v1.dist = 0

    while(len(vset)!=0):
        v = get_unknown_min()
        print(v.vid,v.dist,v.outList)
        v.know = True
        for w in v.outList:#w为索引
            if(vlist[w].know is True):
                continue
            if(vlist[w].dist == float('inf')):
                vlist[w].dist = v.dist + edges[(v.vid,w)]
                vlist[w].prev = v.vid
            else:
                if((v.dist + edges[(v.vid,w)])<vlist[w].dist):
                    vlist[w].dist = v.dist + edges[(v.vid,w)]
                    vlist[w].prev = v.vid
                else:#原路径长更小,没有必要更新
                    pass
main()
print('v1.prev:',v1.prev,'v1.dist',v1.dist)
print('v2.prev:',v2.prev,'v2.dist',v2.dist)
print('v3.prev:',v3.prev,'v3.dist',v3.dist)
print('v4.prev:',v4.prev,'v4.dist',v4.dist)
print('v5.prev:',v5.prev,'v5.dist',v5.dist)
print('v6.prev:',v6.prev,'v6.dist',v6.dist)
print('v7.prev:',v7.prev,'v7.dist',v7.dist)

python3实现Dijkstra算法最短路径的实现

运行结果与数据变化表的最终情况一致。

得到最短路径

把以下代码和以上代码合起来就可以运行成功,使用递归的思想来做:

def real_get_traj(start,index):
    traj_list = []
    def get_traj(index):#参数是顶点在vlist中的索引
        if(index == start):#终点
            traj_list.append(index)
            print(traj_list[::-1])#反转list
            return
        if(vlist[index].dist == float('inf')):
            print('从起点到该顶点根本没有路径')
            return
        traj_list.append(index)
        get_traj(vlist[index].prev)
    get_traj(index)
    print('该最短路径的长度为',vlist[index].dist)

real_get_traj(1,3)
real_get_traj(1,6)

python3实现Dijkstra算法最短路径的实现

如图所示,从v1到v3的最短路径为:[1, 4, 3]
从v1到v6的最短路径为:[1, 4, 7, 6]

负权边

python3实现Dijkstra算法最短路径的实现

Dijkstra算法要求边上的权值不能为负数,不然就会出错。如上,本来最短路径是012,但由于算法是贪心的,所以只会直接选择到2

算法改进(若为无圈图)

注意,只有有向无圈图才有拓扑排序。

如果知道图是无圈图,那么我们可以通过改变声明顶点为known的顺序(原本这个顺序是,每次从unknown里面找出个最小dist的顶点),或者叫做顶点选取法则,来改进Dijkstra算法。新法则以拓扑排序选择顶点。由于选择和更新(每次选择和更新完成后,就会变成数据变化表中的某一种情况)可以在拓扑排序执行的时候进行,因此算法能一趟完成。

因为当一个顶点v被选取以后,按照拓扑排序的法则它肯定没有任何unknown顶点到v(指明方向)的入边,因为v的距离 d v d_v dv​不可能再下降了(因为根本没有别的路到v了),所以这种选择方法是可行的。

使用这种方法不需要优先队列。

到此这篇关于python3实现Dijkstra算法最短路径的实现的文章就介绍到这了,更多相关python3 最短路径内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
python引用DLL文件的方法
May 11 Python
Python随手笔记第一篇(2)之初识列表和元组
Jan 23 Python
Python 高级专用类方法的实例详解
Sep 11 Python
Python实现按当前日期(年、月、日)创建多级目录的方法
Apr 26 Python
详解如何将python3.6软件的py文件打包成exe程序
Oct 09 Python
Python Django Cookie 简单用法解析
Aug 13 Python
pytorch 实现将自己的图片数据处理成可以训练的图片类型
Jan 08 Python
Python图像处理库PIL的ImageFilter模块使用介绍
Feb 26 Python
Python退出时强制运行一段代码的实现方法
Apr 29 Python
Python如何急速下载第三方库详解
Nov 02 Python
Python中读取文件名中的数字的实例详解
Dec 25 Python
python实现计算图形面积
Feb 22 Python
pytorch 中autograd.grad()函数的用法说明
python3实现无权最短路径的方法
Python入门之基础语法详解
May 11 #Python
如何利用Matlab制作一款真正的拼图小游戏
Python机器学习之逻辑回归
Python Pandas知识点之缺失值处理详解
Pytorch实现图像识别之数字识别(附详细注释)
You might like
php 获得汉字拼音首字母的函数
2009/08/01 PHP
数据库查询记录php 多行多列显示
2009/08/15 PHP
PHP排序算法的复习和总结
2012/02/15 PHP
解析php中反射的应用
2013/06/18 PHP
微信公众平台开发-微信服务器IP接口实例(含源码)
2017/03/05 PHP
jquery JSON的解析方式
2009/07/25 Javascript
JavaScript 和 Java 的区别浅析
2013/07/31 Javascript
用jquery的方法制作一个简单的导航栏
2014/06/23 Javascript
JS基于构造函数实现的菜单滑动显隐效果【测试可用】
2016/06/21 Javascript
Angularjs 1.3 中的$parse实例代码
2017/09/14 Javascript
200行代码实现blockchain 区块链实例详解
2018/03/14 Javascript
vue axios请求频繁时取消上一次请求的方法
2018/11/10 Javascript
详解微信小程序开发用户授权登陆
2019/04/24 Javascript
通过javascript实现段落的收缩与展开
2019/06/26 Javascript
python实现的希尔排序算法实例
2015/07/01 Python
浅析Python的web.py框架中url的设定方法
2016/07/11 Python
Windows下Anaconda的安装和简单使用方法
2018/01/04 Python
Python装饰器原理与用法分析
2018/04/30 Python
解决Pycharm中import时无法识别自己写的程序方法
2018/05/18 Python
django在保存图像的同时压缩图像示例代码详解
2020/02/11 Python
Python面向对象中类(class)的简单理解与用法分析
2020/02/21 Python
python之MSE、MAE、RMSE的使用
2020/02/24 Python
PyTorch中torch.tensor与torch.Tensor的区别详解
2020/05/18 Python
Python使用Matlab命令过程解析
2020/06/04 Python
最简单的matplotlib安装教程(小白)
2020/07/28 Python
pandas处理csv文件的方法步骤
2020/10/16 Python
英国最大的割草机购买网站:Just Lawnmowers
2019/11/02 全球购物
Javascript如何发送一个Ajax请求
2015/01/26 面试题
计算机开发个人求职信范文
2013/09/26 职场文书
护士自荐信怎么写
2013/10/18 职场文书
会计找工作求职信范文
2013/12/09 职场文书
《日月潭》教学反思
2014/02/28 职场文书
土木工程师职业规划范文
2014/03/07 职场文书
初中家长意见
2015/06/03 职场文书
python开发实时可视化仪表盘的示例
2021/05/07 Python
Win11怎么解除儿童账号限制?Win11解除微软儿童账号限制方法
2022/07/07 数码科技