Johnson 全源最短路径算法
解决单源最短路径问题(Single Source Shortest Paths Problem)的算法包括:
- Dijkstra 单源最短路径算法:时间复杂度为 O(E + VlogV),要求权值非负;
- Bellman-Ford 单源最短路径算法:时间复杂度为 O(VE),适用于带负权值情况;
对于全源最短路径问题(All-Pairs Shortest Paths Problem),可以认为是单源最短路径问题的推广,即分别以每个顶点作为源顶点并求其至其它顶点的最短距离。例如,对每个顶点应用 Bellman-Ford 算法,则可得到所有顶点间的最短路径的运行时间为 O(V2E),由于图中顶点都是连通的,而边的数量可能会比顶点更多,这个时间没有比 Floyd-Warshall 全源最短路径算法 O(V3) 更优。那么,再试下对每个顶点应用 Dijkstra 算法,则可得到所有顶点间的最短路径的运行时间为 O(VE + V2logV),看起来优于 Floyd-Warshall 算法的 O(V3),所以看起来使用基于 Dijkstra 算法的改进方案好像更好,但问题是 Dijkstra 算法要求图中所有边的权值非负,不适合通用的情况。
在 1977 年,Donald B. Johnson 提出了对所有边的权值进行 "re-weight" 的算法,使得边的权值非负,进而可以使用 Dijkstra 算法进行最短路径的计算。
我们先自己思考下如何进行 "re-weight" 操作,比如,简单地对每条边的权值加上一个较大的正数,使其非负,是否可行?
1 1 1 s-----a-----b-----c \ / \ / \______/ 4
比如上面的图中,共 4 条边,权值分别为 1,1,1,4。当前 s --> c 的最短路径是 {s-a, a-b, b-c} 即 1+1+1=3。而如果将所有边的权值加 1,则最短路径就会变成 {s-c} 的 5,因为 2+2+2=6,实际上导致了最短路径的变化,显然是错误的。
那么,Johnson 算法是如何对边的权值进行 "re-weight" 的呢?以下面的图 G 为例,有 4 个顶点和 5 条边。
首先,新增一个源顶点 4,并使其与所有顶点连通,新边赋权值为 0,如下图所示。
使用 Bellman-Ford 算法 计算新的顶点到所有其它顶点的最短路径,则从 4 至 {0, 1, 2, 3} 的最短路径分别是 {0, -5, -1, 0}。即有 h[] = {0, -5, -1, 0}。当得到这个 h[] 信息后,将新增的顶点 4 移除,然后使用如下公式对所有边的权值进行 "re-weight":
w(u, v) = w(u, v) + (h[u] - h[v]).
则可得到下图中的结果:
此时,所有边的权值已经被 "re-weight" 为非负。此时,就可以利用 Dijkstra 算法对每个顶点分别进行最短路径的计算了。
Johnson 算法描述如下:
- 给定图 G = (V, E),增加一个新的顶点 s,使 s 指向图 G 中的所有顶点都建立连接,设新的图为 G’;
- 对图 G’ 中顶点 s 使用 Bellman-Ford 算法计算单源最短路径,得到结果 h[] = {h[0], h[1], .. h[V-1]};
- 对原图 G 中的所有边进行 "re-weight",即对于每个边 (u, v),其新的权值为 w(u, v) + (h[u] - h[v]);
- 移除新增的顶点 s,对每个顶点运行 Dijkstra 算法求得最短路径;
Johnson 算法的运行时间为 O(V2logV + VE)。
Johnson 算法伪码实现如下:
Johnson 算法 C# 代码实现如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 5 namespace GraphAlgorithmTesting 6 { 7 class Program 8 { 9 static void Main(string[] args) 10 { 11 // build a directed and negative weighted graph 12 Graph directedGraph1 = new Graph(5); 13 directedGraph1.AddEdge(0, 1, -1); 14 directedGraph1.AddEdge(0, 2, 4); 15 directedGraph1.AddEdge(1, 2, 3); 16 directedGraph1.AddEdge(1, 3, 2); 17 directedGraph1.AddEdge(1, 4, 2); 18 directedGraph1.AddEdge(3, 2, 5); 19 directedGraph1.AddEdge(3, 1, 1); 20 directedGraph1.AddEdge(4, 3, -3); 21 22 Console.WriteLine(); 23 Console.WriteLine("Graph Vertex Count : {0}", directedGraph1.VertexCount); 24 Console.WriteLine("Graph Edge Count : {0}", directedGraph1.EdgeCount); 25 Console.WriteLine(); 26 27 int[,] distSet1 = directedGraph1.Johnsons(); 28 PrintSolution(directedGraph1, distSet1); 29 30 // build a directed and positive weighted graph 31 Graph directedGraph2 = new Graph(4); 32 directedGraph2.AddEdge(0, 1, 5); 33 directedGraph2.AddEdge(0, 3, 10); 34 directedGraph2.AddEdge(1, 2, 3); 35 directedGraph2.AddEdge(2, 3, 1); 36 37 Console.WriteLine(); 38 Console.WriteLine("Graph Vertex Count : {0}", directedGraph2.VertexCount); 39 Console.WriteLine("Graph Edge Count : {0}", directedGraph2.EdgeCount); 40 Console.WriteLine(); 41 42 int[,] distSet2 = directedGraph2.Johnsons(); 43 PrintSolution(directedGraph2, distSet2); 44 45 Console.ReadKey(); 46 } 47 48 private static void PrintSolution(Graph g, int[,] distSet) 49 { 50 Console.Write("\t"); 51 for (int i = 0; i < g.VertexCount; i++) 52 { 53 Console.Write(i + "\t"); 54 } 55 Console.WriteLine(); 56 Console.Write("\t"); 57 for (int i = 0; i < g.VertexCount; i++) 58 { 59 Console.Write("-" + "\t"); 60 } 61 Console.WriteLine(); 62 for (int i = 0; i < g.VertexCount; i++) 63 { 64 Console.Write(i + "|\t"); 65 for (int j = 0; j < g.VertexCount; j++) 66 { 67 if (distSet[i, j] == int.MaxValue) 68 { 69 Console.Write("INF" + "\t"); 70 } 71 else 72 { 73 Console.Write(distSet[i, j] + "\t"); 74 } 75 } 76 Console.WriteLine(); 77 } 78 } 79 80 class Edge 81 { 82 public Edge(int begin, int end, int weight) 83 { 84 this.Begin = begin; 85 this.End = end; 86 this.Weight = weight; 87 } 88 89 public int Begin { get; private set; } 90 public int End { get; private set; } 91 public int Weight { get; private set; } 92 93 public void Reweight(int newWeight) 94 { 95 this.Weight = newWeight; 96 } 97 98 public override string ToString() 99 { 100 return string.Format( 101 "Begin[{0}], End[{1}], Weight[{2}]", 102 Begin, End, Weight); 103 } 104 } 105 106 class Graph 107 { 108 private Dictionary<int, List<Edge>> _adjacentEdges 109 = new Dictionary<int, List<Edge>>(); 110 111 public Graph(int vertexCount) 112 { 113 this.VertexCount = vertexCount; 114 } 115 116 public int VertexCount { get; private set; } 117 118 public int EdgeCount 119 { 120 get 121 { 122 return _adjacentEdges.Values.SelectMany(e => e).Count(); 123 } 124 } 125 126 public void AddEdge(int begin, int end, int weight) 127 { 128 if (!_adjacentEdges.ContainsKey(begin)) 129 { 130 var edges = new List<Edge>(); 131 _adjacentEdges.Add(begin, edges); 132 } 133 134 _adjacentEdges[begin].Add(new Edge(begin, end, weight)); 135 } 136 137 public void AddEdge(Edge edge) 138 { 139 AddEdge(edge.Begin, edge.End, edge.Weight); 140 } 141 142 public void AddEdges(IEnumerable<Edge> edges) 143 { 144 foreach (var edge in edges) 145 { 146 AddEdge(edge); 147 } 148 } 149 150 public IEnumerable<Edge> GetAllEdges() 151 { 152 return _adjacentEdges.Values.SelectMany(e => e); 153 } 154 155 public int[,] Johnsons() 156 { 157 // distSet[,] will be the output matrix that will finally have the shortest 158 // distances between every pair of vertices 159 int[,] distSet = new int[VertexCount, VertexCount]; 160 161 for (int i = 0; i < VertexCount; i++) 162 { 163 for (int j = 0; j < VertexCount; j++) 164 { 165 distSet[i, j] = int.MaxValue; 166 } 167 } 168 for (int i = 0; i < VertexCount; i++) 169 { 170 distSet[i, i] = 0; 171 } 172 173 // step 1: add new vertex s and connect to all vertices 174 Graph g = new Graph(this.VertexCount + 1); 175 g.AddEdges(this.GetAllEdges()); 176 177 int s = this.VertexCount; 178 for (int i = 0; i < this.VertexCount; i++) 179 { 180 g.AddEdge(s, i, 0); 181 } 182 183 // step 2: use Bellman-Ford to evaluate shortest paths from s 184 int[] h = g.BellmanFord(s); 185 186 // step 3: re-weighting edges of the original graph 187 // w(u, v) = w(u, v) + (h[u] - h[v]) 188 foreach (var edge in this.GetAllEdges()) 189 { 190 edge.Reweight(edge.Weight + (h[edge.Begin] - h[edge.End])); 191 } 192 193 // step 4: use Dijkstra for each edges 194 for (int begin = 0; begin < this.VertexCount; begin++) 195 { 196 int[] dist = this.Dijkstra(begin); 197 for (int end = 0; end < dist.Length; end++) 198 { 199 if (dist[end] != int.MaxValue) 200 { 201 distSet[begin, end] = dist[end] - (h[begin] - h[end]); 202 } 203 } 204 } 205 206 return distSet; 207 } 208 209 public int[,] FloydWarshell() 210 { 211 /* distSet[,] will be the output matrix that will finally have the shortest 212 distances between every pair of vertices */ 213 int[,] distSet = new int[VertexCount, VertexCount]; 214 215 for (int i = 0; i < VertexCount; i++) 216 { 217 for (int j = 0; j < VertexCount; j++) 218 { 219 distSet[i, j] = int.MaxValue; 220 } 221 } 222 for (int i = 0; i < VertexCount; i++) 223 { 224 distSet[i, i] = 0; 225 } 226 227 /* Initialize the solution matrix same as input graph matrix. Or 228 we can say the initial values of shortest distances are based 229 on shortest paths considering no intermediate vertex. */ 230 foreach (var edge in _adjacentEdges.Values.SelectMany(e => e)) 231 { 232 distSet[edge.Begin, edge.End] = edge.Weight; 233 } 234 235 /* Add all vertices one by one to the set of intermediate vertices. 236 ---> Before start of a iteration, we have shortest distances between all 237 pairs of vertices such that the shortest distances consider only the 238 vertices in set {0, 1, 2, .. k-1} as intermediate vertices. 239 ---> After the end of a iteration, vertex no. k is added to the set of 240 intermediate vertices and the set becomes {0, 1, 2, .. k} */ 241 for (int k = 0; k < VertexCount; k++) 242 { 243 // Pick all vertices as source one by one 244 for (int i = 0; i < VertexCount; i++) 245 { 246 // Pick all vertices as destination for the above picked source 247 for (int j = 0; j < VertexCount; j++) 248 { 249 // If vertex k is on the shortest path from 250 // i to j, then update the value of distSet[i,j] 251 if (distSet[i, k] != int.MaxValue 252 && distSet[k, j] != int.MaxValue 253 && distSet[i, k] + distSet[k, j] < distSet[i, j]) 254 { 255 distSet[i, j] = distSet[i, k] + distSet[k, j]; 256 } 257 } 258 } 259 } 260 261 return distSet; 262 } 263 264 public int[] BellmanFord(int source) 265 { 266 // distSet[i] will hold the shortest distance from source to i 267 int[] distSet = new int[VertexCount]; 268 269 // Step 1: Initialize distances from source to all other vertices as INFINITE 270 for (int i = 0; i < VertexCount; i++) 271 { 272 distSet[i] = int.MaxValue; 273 } 274 distSet[source] = 0; 275 276 // Step 2: Relax all edges |V| - 1 times. A simple shortest path from source 277 // to any other vertex can have at-most |V| - 1 edges 278 for (int i = 1; i <= VertexCount - 1; i++) 279 { 280 foreach (var edge in _adjacentEdges.Values.SelectMany(e => e)) 281 { 282 int u = edge.Begin; 283 int v = edge.End; 284 int weight = edge.Weight; 285 286 if (distSet[u] != int.MaxValue 287 && distSet[u] + weight < distSet[v]) 288 { 289 distSet[v] = distSet[u] + weight; 290 } 291 } 292 } 293 294 // Step 3: check for negative-weight cycles. The above step guarantees 295 // shortest distances if graph doesn't contain negative weight cycle. 296 // If we get a shorter path, then there is a cycle. 297 foreach (var edge in _adjacentEdges.Values.SelectMany(e => e)) 298 { 299 int u = edge.Begin; 300 int v = edge.End; 301 int weight = edge.Weight; 302 303 if (distSet[u] != int.MaxValue 304 && distSet[u] + weight < distSet[v]) 305 { 306 Console.WriteLine("Graph contains negative weight cycle."); 307 } 308 } 309 310 return distSet; 311 } 312 313 public int[] Dijkstra(int source) 314 { 315 // dist[i] will hold the shortest distance from source to i 316 int[] distSet = new int[VertexCount]; 317 318 // sptSet[i] will true if vertex i is included in shortest 319 // path tree or shortest distance from source to i is finalized 320 bool[] sptSet = new bool[VertexCount]; 321 322 // initialize all distances as INFINITE and stpSet[] as false 323 for (int i = 0; i < VertexCount; i++) 324 { 325 distSet[i] = int.MaxValue; 326 sptSet[i] = false; 327 } 328 329 // distance of source vertex from itself is always 0 330 distSet[source] = 0; 331 332 // find shortest path for all vertices 333 for (int i = 0; i < VertexCount - 1; i++) 334 { 335 // pick the minimum distance vertex from the set of vertices not 336 // yet processed. u is always equal to source in first iteration. 337 int u = CalculateMinDistance(distSet, sptSet); 338 339 // mark the picked vertex as processed 340 sptSet[u] = true; 341 342 // update dist value of the adjacent vertices of the picked vertex. 343 for (int v = 0; v < VertexCount; v++) 344 { 345 // update dist[v] only if is not in sptSet, there is an edge from 346 // u to v, and total weight of path from source to v through u is 347 // smaller than current value of dist[v] 348 if (!sptSet[v] 349 && distSet[u] != int.MaxValue 350 && _adjacentEdges.ContainsKey(u) 351 && _adjacentEdges[u].Exists(e => e.End == v)) 352 { 353 int d = _adjacentEdges[u].Single(e => e.End == v).Weight; 354 if (distSet[u] + d < distSet[v]) 355 { 356 distSet[v] = distSet[u] + d; 357 } 358 } 359 } 360 } 361 362 return distSet; 363 } 364 365 private int CalculateMinDistance(int[] distSet, bool[] sptSet) 366 { 367 int minDistance = int.MaxValue; 368 int minDistanceIndex = -1; 369 370 for (int v = 0; v < VertexCount; v++) 371 { 372 if (!sptSet[v] && distSet[v] <= minDistance) 373 { 374 minDistance = distSet[v]; 375 minDistanceIndex = v; 376 } 377 } 378 379 return minDistanceIndex; 380 } 381 } 382 } 383 }
运行结果如下:
参考资料
- 广度优先搜索
- 深度优先搜索
- Breadth First Traversal for a Graph
- Depth First Traversal for a Graph
- Dijkstra 单源最短路径算法
- Bellman-Ford 单源最短路径算法
- Bellman–Ford algorithm
- Introduction To Algorithm
- Floyd-Warshall's algorithm
- Bellman-Ford algorithm for single-source shortest paths
- Dynamic Programming | Set 23 (Bellman–Ford Algorithm)
- Dynamic Programming | Set 16 (Floyd Warshall Algorithm)
- Johnson’s algorithm for All-pairs shortest paths
- Floyd-Warshall's algorithm
- 最短路径算法--Dijkstra算法,Bellmanford算法,Floyd算法,Johnson算法
- QuickGraph, Graph Data Structures And Algorithms for .NET
- CHAPTER 26: ALL-PAIRS SHORTEST PATHS
本篇文章《Johnson 全源最短路径算法》由 Dennis Gao 发表自博客园,未经作者本人同意禁止任何形式的转载,任何自动或人为的爬虫转载行为均为耍流氓。