Floyd-Warshall 算法采用動態規划方案來解決在一個有向圖 G = (V, E) 上每對頂點間的最短路徑問題,即全源最短路徑問題(All-Pairs Shortest Paths Problem),其中圖 G 允許存在權值為負的邊,但不存在權值為負的回路。Floyd-Warshall 算法的運行時間為 Θ(V3)。
Floyd-Warshall 算法由 Robert Floyd 於 1962 年提出,但其實質上與 Bernad Roy 於 1959 年和 Stephen Warshall 於 1962 年提出的算法相同。
解決單源最短路徑問題的方案有 Dijkstra 算法和 Bellman-Ford 算法,對於全源最短路徑問題可以認為是單源最短路徑問題(Single Source Shortest Paths Problem)的推廣,即分別以每個頂點作為源頂點並求其至其它頂點的最短距離。更通用的全源最短路徑算法包括:
- 針對稠密圖的 Floyd-Warshall 算法:時間復雜度為 O(V3);
- 針對稀疏圖的 Johnson 算法:時間復雜度為 O(V2logV + VE);
最短路徑算法中的最優子結構指的是兩頂點之間的最短路徑包括路徑上其它頂點的最短路徑。具體描述為:對於給定的帶權圖 G = (V, E),設 p = <v1, v2, …,vk> 是從 v1 到 vk 的最短路徑,那么對於任意 i 和 j,1 ≤ i ≤ j ≤ k,pij = <vi, vi+1, …, vj> 為 p 中頂點 vi 到 vj 的子路徑,那么 pij 是頂點 vi 到 vj 的最短路徑。
![]()
Floyd-Warshall 算法的設計基於了如下觀察。設帶權圖 G = (V, E) 中的所有頂點 V = {1, 2, . . . , n},考慮一個頂點子集 {1, 2, . . . , k}。對於任意對頂點 i, j,考慮從頂點 i 到 j 的所有路徑的中間頂點都來自該子集 {1, 2, . . . , k},設 p 是該子集中的最短路徑。Floyd-Warshall 算法描述了 p 與 i, j 間最短路徑及中間頂點集合 {1, 2, . . . , k - 1} 的關系,該關系依賴於 k 是否是路徑 p 上的一個中間頂點。

算法偽碼如下:

最短路徑算法的設計都使用了松弛(relaxation)技術。在算法開始時只知道圖中邊的權值,然后隨着處理逐漸得到各對頂點的最短路徑的信息,算法會逐漸更新這些信息,每步都會檢查是否可以找到一條路徑比當前已有路徑更短,這一過程通常稱為松弛(relaxation)。
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 int[,] graph = new int[9, 9] 12 { 13 {0, 4, 0, 0, 0, 0, 0, 8, 0}, 14 {4, 0, 8, 0, 0, 0, 0, 11, 0}, 15 {0, 8, 0, 7, 0, 4, 0, 0, 2}, 16 {0, 0, 7, 0, 9, 14, 0, 0, 0}, 17 {0, 0, 0, 9, 0, 10, 0, 0, 0}, 18 {0, 0, 4, 0, 10, 0, 2, 0, 0}, 19 {0, 0, 0, 14, 0, 2, 0, 1, 6}, 20 {8, 11, 0, 0, 0, 0, 1, 0, 7}, 21 {0, 0, 2, 0, 0, 0, 6, 7, 0} 22 }; 23 24 Graph g = new Graph(graph.GetLength(0)); 25 for (int i = 0; i < graph.GetLength(0); i++) 26 { 27 for (int j = 0; j < graph.GetLength(1); j++) 28 { 29 if (graph[i, j] > 0) 30 g.AddEdge(i, j, graph[i, j]); 31 } 32 } 33 34 Console.WriteLine("Graph Vertex Count : {0}", g.VertexCount); 35 Console.WriteLine("Graph Edge Count : {0}", g.EdgeCount); 36 Console.WriteLine(); 37 38 int[,] distSet = g.FloydWarshell(); 39 PrintSolution(g, distSet); 40 41 // build a directed and negative weighted graph 42 Graph directedGraph1 = new Graph(5); 43 directedGraph1.AddEdge(0, 1, -1); 44 directedGraph1.AddEdge(0, 2, 4); 45 directedGraph1.AddEdge(1, 2, 3); 46 directedGraph1.AddEdge(1, 3, 2); 47 directedGraph1.AddEdge(1, 4, 2); 48 directedGraph1.AddEdge(3, 2, 5); 49 directedGraph1.AddEdge(3, 1, 1); 50 directedGraph1.AddEdge(4, 3, -3); 51 52 Console.WriteLine(); 53 Console.WriteLine("Graph Vertex Count : {0}", directedGraph1.VertexCount); 54 Console.WriteLine("Graph Edge Count : {0}", directedGraph1.EdgeCount); 55 Console.WriteLine(); 56 57 int[,] distSet1 = directedGraph1.FloydWarshell(); 58 PrintSolution(directedGraph1, distSet1); 59 60 // build a directed and positive weighted graph 61 Graph directedGraph2 = new Graph(4); 62 directedGraph2.AddEdge(0, 1, 5); 63 directedGraph2.AddEdge(0, 3, 10); 64 directedGraph2.AddEdge(1, 2, 3); 65 directedGraph2.AddEdge(2, 3, 1); 66 67 Console.WriteLine(); 68 Console.WriteLine("Graph Vertex Count : {0}", directedGraph2.VertexCount); 69 Console.WriteLine("Graph Edge Count : {0}", directedGraph2.EdgeCount); 70 Console.WriteLine(); 71 72 int[,] distSet2 = directedGraph2.FloydWarshell(); 73 PrintSolution(directedGraph2, distSet2); 74 75 Console.ReadKey(); 76 } 77 78 private static void PrintSolution(Graph g, int[,] distSet) 79 { 80 Console.Write("\t"); 81 for (int i = 0; i < g.VertexCount; i++) 82 { 83 Console.Write(i + "\t"); 84 } 85 Console.WriteLine(); 86 Console.Write("\t"); 87 for (int i = 0; i < g.VertexCount; i++) 88 { 89 Console.Write("-" + "\t"); 90 } 91 Console.WriteLine(); 92 for (int i = 0; i < g.VertexCount; i++) 93 { 94 Console.Write(i + "|\t"); 95 for (int j = 0; j < g.VertexCount; j++) 96 { 97 if (distSet[i, j] == int.MaxValue) 98 { 99 Console.Write("INF" + "\t"); 100 } 101 else 102 { 103 Console.Write(distSet[i, j] + "\t"); 104 } 105 } 106 Console.WriteLine(); 107 } 108 } 109 110 class Edge 111 { 112 public Edge(int begin, int end, int weight) 113 { 114 this.Begin = begin; 115 this.End = end; 116 this.Weight = weight; 117 } 118 119 public int Begin { get; private set; } 120 public int End { get; private set; } 121 public int Weight { get; private set; } 122 123 public override string ToString() 124 { 125 return string.Format( 126 "Begin[{0}], End[{1}], Weight[{2}]", 127 Begin, End, Weight); 128 } 129 } 130 131 class Graph 132 { 133 private Dictionary<int, List<Edge>> _adjacentEdges 134 = new Dictionary<int, List<Edge>>(); 135 136 public Graph(int vertexCount) 137 { 138 this.VertexCount = vertexCount; 139 } 140 141 public int VertexCount { get; private set; } 142 143 public int EdgeCount 144 { 145 get 146 { 147 return _adjacentEdges.Values.SelectMany(e => e).Count(); 148 } 149 } 150 151 public void AddEdge(int begin, int end, int weight) 152 { 153 if (!_adjacentEdges.ContainsKey(begin)) 154 { 155 var edges = new List<Edge>(); 156 _adjacentEdges.Add(begin, edges); 157 } 158 159 _adjacentEdges[begin].Add(new Edge(begin, end, weight)); 160 } 161 162 public int[,] FloydWarshell() 163 { 164 /* distSet[,] will be the output matrix that will finally have the shortest 165 distances between every pair of vertices */ 166 int[,] distSet = new int[VertexCount, VertexCount]; 167 168 for (int i = 0; i < VertexCount; i++) 169 { 170 for (int j = 0; j < VertexCount; j++) 171 { 172 distSet[i, j] = int.MaxValue; 173 } 174 } 175 for (int i = 0; i < VertexCount; i++) 176 { 177 distSet[i, i] = 0; 178 } 179 180 /* Initialize the solution matrix same as input graph matrix. Or 181 we can say the initial values of shortest distances are based 182 on shortest paths considering no intermediate vertex. */ 183 foreach (var edge in _adjacentEdges.Values.SelectMany(e => e)) 184 { 185 distSet[edge.Begin, edge.End] = edge.Weight; 186 } 187 188 /* Add all vertices one by one to the set of intermediate vertices. 189 ---> Before start of a iteration, we have shortest distances between all 190 pairs of vertices such that the shortest distances consider only the 191 vertices in set {0, 1, 2, .. k-1} as intermediate vertices. 192 ---> After the end of a iteration, vertex no. k is added to the set of 193 intermediate vertices and the set becomes {0, 1, 2, .. k} */ 194 for (int k = 0; k < VertexCount; k++) 195 { 196 // Pick all vertices as source one by one 197 for (int i = 0; i < VertexCount; i++) 198 { 199 // Pick all vertices as destination for the above picked source 200 for (int j = 0; j < VertexCount; j++) 201 { 202 // If vertex k is on the shortest path from 203 // i to j, then update the value of distSet[i,j] 204 if (distSet[i, k] != int.MaxValue 205 && distSet[k, j] != int.MaxValue 206 && distSet[i, k] + distSet[k, j] < distSet[i, j]) 207 { 208 distSet[i, j] = distSet[i, k] + distSet[k, j]; 209 } 210 } 211 } 212 } 213 214 return distSet; 215 } 216 } 217 } 218 }
運行結果如下:

參考資料
- 廣度優先搜索
- 深度優先搜索
- 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
本篇文章《Floyd-Warshall 全源最短路徑算法》由 Dennis Gao 發表自博客園,未經作者本人同意禁止任何形式的轉載,任何自動或人為的爬蟲轉載行為均為耍流氓。
