最短路径:迪杰斯特拉算法实现


title: ‘最短路径:迪杰斯特拉算法实现’
date: 2019-09-03 19:42:02
tags: python,数据结构
categories: 计算机理论

最短路径

dijkstra算法

算法原理

在看迪杰斯特拉算法之前,可以先回顾下BFS算法的过程。BFS的实现是通过一个队列实现。还是这张图

20190825160305

选择假设BFS从A节点开始,A节点出队后,将A的邻接节点B,C入队

20190831190937

然后B出队,D入队,C出队,E入队。整个BFS的流程大概如此,在这之中,可以看到BFS队列中不同节点离A的距离,每个出队的结点对于他的邻接节点的距离都是1,并且在队列中他们也是紧紧挨着的。

假如可以把这些顶点进行排序,然后不断更新队中节点到A的距离值,那么应该可以一步步的获得当前节点到A节点的最短距离了。

该算法有两个难点:

  1. 如何排序

我使用的是python的优先队列,该队列是基于堆这一种数据结构实现的,你也可以自行选择排序算法进行排序

  1. 如何更新距离

在BFS中每个节点到A的距离是固定的,是不会发生更新操作的,这是由于BFS算法实现过程中有个访问标志会标志某个节点是否已被访问,该标志保证了每个节点只访问一次。但是在迪杰斯特拉算法中,这样是不行的,因为想要在每个节点出队后,都要将该结点的邻接节点到目标点(这个例子中是A点)的距离进行比较更新,选择权值和小的。

看下面这个网

20190831193520

当遍历到B的时候A到B有两条路,一条是A-B,另一条是A-C-B,前者的距离为5,比较长,后者的距离为3(1+2)。在迪杰斯特拉中就会选择路径A-C-B这条路径。

在实际的算法实现中,距离的比较是通过一个distance的列表实现的,该列表距离了每个顶点到目标点的最短距离。然后在下一次遍历中不断得更新这个距离就可以了。

构造过程举例

假设还是上面这个图。

20190831193520

要求图中顶点到A的最短距离

初始化

初始化距离列表,inf 表示无穷,A的目标点,所以距离为0

A,0
B,inf
C,inf
D,inf
E,inf
F,inf

初始化优先级队列,目标节点A入队

A,0

当队列不为空时,循环。

第一次构造
  1. A出队,并标记为已访问,遍历A的邻接节点B、C,同时将A到B的距离5(0+5)和A到C的距离1(0+1),与distance列表中的距离进行比较,由于distance中的距离都是无穷,所以distance中C的距离更新为1,B的距离更新为5
A,0
B,5
C,1
D,inf
E,inf
F,inf
  1. B,C节点由于距离被更新了。需要参与下一次比较,所以B、C入队
B,5
C,1

第二次构造

  1. C出队,并标记为已访问,遍历C的邻接节点A,B,D,E,将C-A的距离1,C-B的距离3(1+2)和C-D的距离5(1+4)和C-E的距离9(1+8),与distance列表中的B,C,E的距离进行比较,更新为其中的较小值
A,0
B,3
C,1
D,5
E,9
F,inf
  1. 由于B,D,E的距离被更新了。需要参与下一次的比较,所以B,C,E需要入队,带着他们的更新后的权值
B,5
B,3
D,5
E,9
第三次构造
  1. B出队,并标记为已访问,遍历B的子节点D,C,A,将B-D(3+1=4),B-C(3+2=5),B-A(3+5)的距离分别与distance中的D,C,A距离进行比较,取小的值,发现只有D的距离被更新为了4
A,0
B,3
C,1
D,4
E,9
F,inf
  1. 由于D被更新了,需要参与下一次的比较,所以D入队,带着D更新后的权值
D,4
B,5
D,5
E,9
第四次构造
  1. D出队,并标记为已访问,遍历D的子节点B,C,E,F。将D-B(4+1=5),D-C(4+4=8),D-E(4+3=7),D-F(4+6=10)的距离分别与distance中的B,C,E,F距离进行比较,取小的值
A,0
B,3
C,1
D,4
E,7
F,10
  1. 由于只有E,F的距离被更新为7,和10,所以E,F需要带着他们更新后的权值入队,参与下一次的比较。
B,5
D,5
E,7
E,9
F,10
第五次构造
  1. 队首B出队,由于B被标记已访问,所以直接扔掉,进入下一个循环
D,5
E,7
E,9
F,10
  1. 队首D出队,由于D已经被标记已访问,扔掉。进入下一个循环
E,7
E,9
F,10
  1. 队首E出队,标记为已访问。遍历其邻接节点C,D,将E-C(7+4=11),E-D(7+3=10)与distance中的C,D值进行比较,取小的值,发现C,D都不需要更新。

  2. 由于没有节点被更新,所以没有节点入队。此时的distance如下图

A,0
B,3
C,1
D,4
E,7
F,10
第六次构造
  1. 队首E出队,由于E被标记为已访问,扔掉,进入下一个循环
E,9
F,10
  1. 队首E出队,由于E被标记为已访问,扔掉,进入下一个循环
F,10
  1. 队首F出队,发现他没有子节点,所以distance不会被更新,队列将不会加入新的结点。此时的distance如下图
A,0
B,3
C,1
D,4
E,7
F,10
第七次构造

由于此时队列为空,所以循环结束,迪杰斯特拉算法求解完毕!此时的distance就是每个节点到目标点A的最短距离了。

A,0
B,3
C,1
D,4
E,7
F,10

迪杰斯特拉算法就是基于这种"宽度优先遍历"的思想,按路径的长度选择下一个最短节点然后逐步扩张(这一点也很像用MST性质实现的prim算法)。这个算法在探索中也会更新已经节点的最短路径,每一步都可以找到一个确定的最短路径,这就是典型的动态规划思想(在计算中保留一些信息,用来支持下一步的决策信息)

算法实现

    # 迪杰斯特拉法算最短路径
    def dijkstra(self, start):
        if not self._invalid(start):
            raise GraphError("不存在" + start + "这样的顶点")
        graph = self._graph
        pqueue = []  # 优先级队列
        heapq.heappush(pqueue, (0, start))  # 根顶点进队,最高优先级
        seen = set()  # 记录访问过的顶点
        parent = {
    
    start: None}  # 生成树
        distance = self.__init_distance(start)  # 初始化距离
        while pqueue.__len__() > 0:
            pair = heapq.heappop(pqueue)  # pop弹出的是元组,第一个参数是距离(优先级),第二个是顶点
            dist = pair[0]
            vertex = pair[1]
            seen.add(vertex)  # 记录访问过的顶点
            nodes = graph[vertex].keys()  # 获取其顶点的邻接顶点
            for node in nodes:
                if node not in seen:
                    if dist + graph[vertex][node] < distance[node]:  # 如果当前顶点到开始顶点的距离小于距离列表中的值,更新距离
                        heapq.heappush(pqueue, (dist + graph[vertex][node], node))
                        parent[node] = vertex
                        distance[node] = dist + graph[vertex][node]
            # 输出遍历结果
            # print(vertex)
        return distance, parent
        pass

测试

20190831205632

可以发现,如刚才推导的结果一模一样。

A,0
B,3
C,1
D,4
E,7
F,10

猜你喜欢

转载自blog.csdn.net/weixin_41154636/article/details/100526990