0
点赞
收藏
分享

微信扫一扫

10.6 全源(All pairs)负权Johnson算法

司马吹风 2022-04-01 阅读 62

目录

1 算法步骤

  Johnson算法分为三步:
  1. 先使用bellman-ford算法,计算单源到其他点的最短路径;
  2. 利用计算结果去重赋权reweight,使得没有负数权重;
  3. 循环调用dijikstra算法计算所有点的权重。
  4. 所有点的权重再换算回来,得到结果。
  复杂度为 O ( n m + n 2 l o g n ) O(nm + n^2 log n) O(nm+n2logn)(计算机学科的对数以2为底),从理论上讲这是目前世界上性能最高的全源负权最短距离算法。
  重赋权的计算也不难。步骤分为以下几步:
  1. 图增加一个新的节点H,使图变成增广图 G ′ G' G,新节点H与图中每一个节点都相连,并且距离为0;
  2. 使用bellman-ford算法计算H与其他点的单源最短路径。
  3. 计算出来后,用以下公式进行重赋权
W 2 ( x , y ) = W 1 ( x , y ) + h ( x ) − h ( y ) W_2(x, y) = W_1(x, y) + h(x) - h(y) W2(x,y)=W1(x,y)+h(x)h(y)
  4. 然后移除新的顶点H,因为这是个虚拟节点,所以在计算完成之后,要将图进行恢复。

2 重赋权为什么非负

  为了解释重赋权公式,我画一张图,图中 h ( x ) h(x) h(x)代表虚拟点h到x的最短路径, h ( y ) h(y) h(y)代表虚拟点h到y的最短路径, w ( x , y ) w(x,y) w(x,y)代表x指向y的边的权重:
在这里插入图片描述
  因为是最短路径,所以必定有以下公式:
h ( x ) + w ( x , y ) ≥ h ( y ) ∴ w ( x , y ) + h ( x ) − h ( y ) ≥ 0 h(x)+w(x,y)\ge h(y)\\ \therefore w(x,y) + h(x) - h(y) \ge 0 h(x)+w(x,y)h(y)w(x,y)+h(x)h(y)0
  所以重赋权之后,一定不会出现负权重。

3 为什么可以重赋权

  但是还有一个问题,为什么可以这样重赋权?我们必须证明一点,重赋权之后还是最短路径。假设 u 0 u_0 u0 v k v_k vk的最短路径为 p p p。在p上,除了 v 0 v_0 v0 v k v_k vk外还有 v 1 , v 2 , … , v k − 1 v_1,v_2,\dots,v_{k-1} v1,v2,,vk1这些点。所以最短路径总权重,也就是最短距离 d ( v 0 , v k ) d(v_0,v_k) d(v0,vk)就是:
d ( v 0 , v k ) = ∑ i = 0 k − 1 w ( v i , v i + 1 ) d(v_0,v_k)=\sum_{i=0}^{k-1}w(v_i,v_{i+1}) d(v0,vk)=i=0k1w(vi,vi+1)
  我们进行重赋权,重赋权公式为:
w ′ ( x , y ) = w ( x , y ) + h ( x ) − h ( y ) w'(x, y) = w(x, y) + h(x) - h(y) w(x,y)=w(x,y)+h(x)h(y)
  那么重赋权之后的最短路径总权重为:
d ′ ( v 0 , v k ) = ∑ i = 0 k − 1 w ′ ( v i , v i + 1 ) = ∑ i = 0 k − 1 w ( v i , v i + 1 ) + h ( v i ) − h ( v i + 1 ) d'(v_0,v_k)=\sum_{i=0}^{k-1}w'(v_i,v_{i+1})=\sum_{i=0}^{k-1}w(v_i,v_{i+1}) + h(v_i) - h(v_{i+1}) d(v0,vk)=i=0k1w(vi,vi+1)=i=0k1w(vi,vi+1)+h(vi)h(vi+1)
  展开之后,发现这是个伸缩数列,中间的 h ( v 1 ) … h ( v k − 1 ) h(v_1) \dots h(v_{k-1}) h(v1)h(vk1)全部是一正一负,相加之后被消去了,只剩下了首尾:
d ′ ( v 0 , v k ) = w ( v 0 , v 1 ) + h ( v 0 ) − h ( v 1 ) + w ( v 1 , v 2 ) + h ( v 1 ) − h ( v 2 ) + ⋯ + w ( v k − 2 , v k − 1 ) + h ( v k − 2 ) − h ( v k − 1 ) + w ( v k − 1 , v k ) + h ( v k − 1 ) − h ( v k ) = h ( v 0 ) − h ( v k ) + ∑ i = 0 k − 1 w ( v i , v i + 1 ) = d ( v 0 , v k ) + h ( v 0 ) − h ( v k ) d'(v_0,v_k)\\ =w(v_0,v_1)+h(v_0)-h(v_1)\\ +w(v_1,v_2)+h(v_1)-h(v_2)\\ +\cdots\\ +w(v_{k-2},v_{k-1})+h(v_{k-2})-h(v_{k-1})\\ +w(v_{k-1},v_k)+h(v_{k-1})-h(v_k)\\ =h(v_0)-h(v_k)+\sum_{i=0}^{k-1}w(v_i,v_{i+1})\\ =d(v_0, v_k) + h(v_0) - h(v_k) d(v0,vk)=w(v0,v1)+h(v0)h(v1)+w(v1,v2)+h(v1)h(v2)++w(vk2,vk1)+h(vk2)h(vk1)+w(vk1,vk)+h(vk1)h(vk)=h(v0)h(vk)+i=0k1w(vi,vi+1)=d(v0,vk)+h(v0)h(vk)
  所以证明了,最短路径上,重赋权之后的权重和是原权重和的重赋权。巧妙地利用伸缩数列,是Johnson算法的精妙之处。而其他博文是很少讲到这里的,感觉有点意思的,有意思的,可以收藏本文,也可以顺势关注一波。

4 小例子

在这里插入图片描述
  创建虚拟节点H
在这里插入图片描述
  重赋权:
在这里插入图片描述

5 python实现

  Python代码如下:

    def johnson(self):
        print("before reweight", self.to_dot())

        # 新加一个虚拟节点
        self.__vertices.append('H')
        n = len(self.__vertices)
        for i in range(0, n - 1):
            self.__edges.append(WeightedEdge(n - 1, i, 0))

        h = self.bellman_ford(n - 1)[0]
        print("before reweight", self.to_dot())
        # 重建权重
        for e in self.__edges:
            e.weight = e.weight + h[e.from_index] - h[e.to_index]


        # 删除后,使用
        self.__vertices.pop()
        self.__edges = self.__edges[0:-n]
        print("after reweight", self.to_dot())
        distance = [None for _ in range(len(self.__vertices))]
        for s in range(len(self.__vertices)):
            dijkstra = self.dijkstra(s)
            print(dijkstra)
            for i, e in enumerate(dijkstra):
                dijkstra[i] = e - h[s] + h[i]
            distance[s] = dijkstra
        # 恢复权重
        for e in self.__edges:
            e.weight = e.weight - h[e.from_index] + h[e.to_index]
        return distance

  测试数据:

    def create_graph(self):
        vertices = ['A', 'B', 'C', 'S']
        edges = [
            WeightedEdge(0, 1, 2),
            WeightedEdge(2, 0, -2),
            WeightedEdge(2, 1, 1),
            WeightedEdge(3, 0, 1),
            WeightedEdge(3, 2, 2)
        ]
        graph = WeightedGraph()
        graph.vertices = vertices
        graph.edges = edges
        return graph

在这里插入图片描述
  测试结果:

[[0, 2, inf, inf], [inf, 0, inf, inf], [-2, 0, 0, inf], [1, 3, inf, 0]]
举报

相关推荐

0 条评论