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算法过程中,有一个数据变化表。

初始状态如上。开始顶点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​作相应的处理。

【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​作相应的处理。

【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,所以也不需要也没有必要调整。

【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

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

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

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

如何使用数据变化表

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

现在我们能找到起点 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已有实现好的优先队列。但实验中报错如下:

意思,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)

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

得到最短路径

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

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)

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

负权边

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

算法改进(若为无圈图)

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

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

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

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

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

(0)

相关推荐

  • python实现最短路径的实例方法

    最短路径问题(python实现) 解决最短路径问题:(如下三种算法) (1)迪杰斯特拉算法(Dijkstra算法) (2)弗洛伊德算法(Floyd算法) (3)SPFA算法 第一种算法: Dijkstra算法 广度优先搜索解决赋权有向图或者无向图的单源最短路径问题.是一种贪心的策略 算法的思路 声明一个数组dis来保存源点到各个顶点的最短距离和一个保存已经找到了最短路径的顶点的集合:T,初始时,原点s的路径权重被赋为0(dis[s]=0).若对于顶点s存在能直接到达的边(s,m),则把dis[m

  • Python使用Dijkstra算法实现求解图中最短路径距离问题详解

    本文实例讲述了Python使用Dijkstra算法实现求解图中最短路径距离问题.分享给大家供大家参考,具体如下: 这里继续前面一篇<Python基于Floyd算法求解最短路径距离问题>的内容,这里要做的是Dijkstra算法,与Floyd算法类似,二者的用途均为求解最短路径距离,在图中有着广泛的应用,二者的原理都是老生常谈了,毕竟本科学习数据结构的同学是不可能不学习这两个算法的,所以在这里我也不再累赘,只简单概述一下这个算法的核心思想: Dijkstra算法的输入有两个参数,一个是原始的数据矩

  • Python数据结构与算法之图的最短路径(Dijkstra算法)完整实例

    本文实例讲述了Python数据结构与算法之图的最短路径(Dijkstra算法).分享给大家供大家参考,具体如下: # coding:utf-8 # Dijkstra算法--通过边实现松弛 # 指定一个点到其他各顶点的路径--单源最短路径 # 初始化图参数 G = {1:{1:0, 2:1, 3:12}, 2:{2:0, 3:9, 4:3}, 3:{3:0, 5:5}, 4:{3:4, 4:0, 5:13, 6:15}, 5:{5:0, 6:4}, 6:{6:0}} # 每次找到离源点最近的一个顶

  • python实现Dijkstra算法的最短路径问题

    迪杰斯特拉(Dijkstra)算法主要是针对没有负值的有向图,求解其中的单一起点到其他顶点的最短路径算法. 1 算法原理 迪杰斯特拉(Dijkstra)算法是一个按照路径长度递增的次序产生的最短路径算法.下图为带权值的有向图,作为程序中的实验数据. 其中,带权值的有向图采用邻接矩阵graph来进行存储,在计算中就是采用n*n的二维数组来进行存储,v0-v5表示数组的索引编号0-5,二维数组的值表示节点之间的权值,若两个节点不能通行,比如,v0->v1不能通行,那么graph[0,1]=+∞ (采

  • python3实现无权最短路径的方法

    问题描述 现有一个有向无权图.如下图所示: 问题:使用某个顶点s作为输入参数,找出从s到所有其他顶点的最短路径. 说明:因为是无权图,因此我们可以为每台边赋值为1.这里选择v3为s作为起点. 问题分析 此时立刻可以说,从s到v3的最短路径是长为0的路径,标记此信息,得到下图. 现在开始寻找从s出发距离为1的顶点.这些顶点肯定是与s邻接的顶点,很明显,v1,v6从s出发只需要一条边就到了.所以,从s出发距离为1的顶点,为v1,v6. 现在开始寻找从s出发距离为2的顶点.这些顶点肯定是与v1,v6(

  • python Dijkstra算法实现最短路径问题的方法

    本文借鉴于张广河教授主编的<数据结构>,对其中的代码进行了完善. 从某源点到其余各顶点的最短路径 Dijkstra算法可用于求解图中某源点到其余各顶点的最短路径.假设G={V,{E}}是含有n个顶点的有向图,以该图中顶点v为源点,使用Dijkstra算法求顶点v到图中其余各顶点的最短路径的基本思想如下: 使用集合S记录已求得最短路径的终点,初始时S={v}. 选择一条长度最小的最短路径,该路径的终点w属于V-S,将w并入S,并将该最短路径的长度记为Dw. 对于V-S中任一顶点是s,将源点到顶点

  • 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算法最短路径问题详解

    1.最短路径问题介绍 问题解释: 从图中的某个顶点出发到达另外一个顶点的所经过的边的权重和最小的一条路径,称为最短路径 解决问题的算法: 迪杰斯特拉算法(Dijkstra算法) 弗洛伊德算法(Floyd算法) SPFA算法 这篇博客,我们就对Dijkstra算法来做一个详细的介绍 2.Dijkstra算法介绍 算法特点: 迪科斯彻算法使用了广度优先搜索解决赋权有向图或者无向图的单源最短路径问题,算法最终得到一个最短路径树.该算法常用于路由算法或者作为其他图算法的一个子模块. 算法的思路 Dijk

  • Dijkstra算法最短路径的C++实现与输出路径

    某个源点到其余各顶点的最短路径 这个算法最开始心里怕怕的,不知道为什么,花了好长时间弄懂了,也写了一遍,又遇到时还是出错了,今天再次写它,心里没那么怕了,耐心研究,懂了之后会好开心的,哈哈 Dijkstra算法: 图G 如图:若要求从顶点1到其余各顶点的最短路径,该咋求: 迪杰斯特拉提出"按最短路径长度递增的次序"产生最短路径. 首先,在所有的这些最短路径中,长度最短的这条路径必定只有一条弧,且它的权值是从源点出发的所有弧上权的最小值,例如:在图G中,从源点1出发有3条弧,其中以弧(1

  • 详解Dijkstra算法之最短路径问题

    一.最短路径问题介绍 问题解释: 从图中的某个顶点出发到达另外一个顶点的所经过的边的权重和最小的一条路径,称为最短路径 解决问题的算法: 迪杰斯特拉算法(Dijkstra算法) 弗洛伊德算法(Floyd算法) SPFA算法 这篇博客,我们就对Dijkstra算法来做一个详细的介绍 二.Dijkstra算法介绍 2.1.算法特点 迪科斯彻算法使用了广度优先搜索解决赋权有向图或者无向图的单源最短路径问题,算法最终得到一个最短路径树.该算法常用于路由算法或者作为其他图算法的一个子模块. 2.2.算法的

  • java实现最短路径算法之Dijkstra算法

    前言 Dijkstra算法是最短路径算法中为人熟知的一种,是单起点全路径算法.该算法被称为是"贪心算法"的成功典范.本文接下来将尝试以最通俗的语言来介绍这个伟大的算法,并赋予java实现代码. 一.知识准备: 1.表示图的数据结构 用于存储图的数据结构有多种,本算法中笔者使用的是邻接矩阵. 图的邻接矩阵存储方式是用两个数组来表示图.一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息. 设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为: 从上面可以看出,无

  • java使用Dijkstra算法实现单源最短路径

    单源最短路径问题,即在图中求出给定顶点到其它任一顶点的最短路径.在弄清楚如何求算单源最短路径问题之前,必须弄清楚最短路径的最优子结构性质. 一.最短路径的最优子结构性质 该性质描述为:如果P(i,j)={Vi....Vk..Vs...Vj}是从顶点i到j的最短路径,k和s是这条路径上的中间顶点,那么P(k,s)必定是从k到s的最短路径.下面证明该性质的正确性. 假设P(i,j)={Vi....Vk..Vs...Vj}是从顶点i到j的最短路径,则有P(i,j)=P(i,k)+P(k,s)+P(s,

  • JS使用Dijkstra算法求解最短路径

    一.Dijkstra算法的思路 Dijkstra算法是针对单源点求最短路径的算法. 其主要思路如下: 1. 将顶点分为两部分:已经知道当前最短路径的顶点集合Q和无法到达顶点集合R. 2. 定义一个距离数组(distance)记录源点到各顶点的距离,下标表示顶点,元素值为距离.源点(start)到自身的距离为0,源点无法到达的顶点的距离就是一个大数(比如Infinity). 3. 以距离数组中值为非Infinity的顶点V为中转跳点,假设V跳转至顶点W的距离加上顶点V至源点的距离还小于顶点W至源点

随机推荐