Dijkstra 算法是一種用於計算帶權有向圖中單源最短路徑(SSSP:Single-Source Shortest Path)的算法,由計算機科學家 Edsger Dijkstra 於 1956 年構思並於 1959 年發表。其解決的問題是:給定圖 G 和源頂點 v,找到從 v 至圖中所有頂點的最短路徑。
Dijkstra 算法采用貪心算法(Greedy Algorithm)范式進行設計。在最短路徑問題中,對於帶權有向圖 G = (V, E),Dijkstra 算法的初始實現版本未使用最小優先隊列實現,其時間復雜度為 O(V2),基於 Fibonacci heap 的最小優先隊列實現版本,其時間復雜度為 O(E + VlogV)。
Bellman-Ford 算法和 Dijkstra 算法同為解決單源最短路徑的算法。對於帶權有向圖 G = (V, E),Dijkstra 算法要求圖 G 中邊的權值均為非負,而 Bellman-Ford 算法能適應一般的情況(即存在負權邊的情況)。一個實現的很好的 Dijkstra 算法比 Bellman-Ford 算法的運行時間 O(V*E) 要低。
Dijkstra 算法描述:
- 創建源頂點 v 到圖中所有頂點的距離的集合 distSet,為圖中的所有頂點指定一個距離值,初始均為 Infinite,源頂點距離為 0;
- 創建 SPT(Shortest Path Tree)集合 sptSet,用於存放包含在 SPT 中的頂點;
- 如果 sptSet 中並沒有包含所有的頂點,則:
- 選中不包含在 sptSet 中的頂點 u,u 為當前 sptSet 中未確認的最短距離頂點;
- 將 u 包含進 sptSet;
- 更新 u 的所有鄰接頂點的距離值;
偽碼實現如下:
1 function Dijkstra(Graph, source): 2 3 dist[source] ← 0 // Distance from source to source 4 prev[source] ← undefined // Previous node in optimal path initialization 5 6 for each vertex v in Graph: // Initialization 7 if v ≠ source // Where v has not yet been removed from Q (unvisited nodes) 8 dist[v] ← infinity // Unknown distance function from source to v 9 prev[v] ← undefined // Previous node in optimal path from source 10 end if 11 add v to Q // All nodes initially in Q (unvisited nodes) 12 end for 13 14 while Q is not empty: 15 u ← vertex in Q with min dist[u] // Source node in first case 16 remove u from Q 17 18 for each neighbor v of u: // where v has not yet been removed from Q. 19 alt ← dist[u] + length(u, v) 20 if alt < dist[v]: // A shorter path to v has been found 21 dist[v] ← alt 22 prev[v] ← u 23 end if 24 end for 25 end while 26 27 return dist[], prev[] 28 29 end function
例如,下面是一個包含 9 個頂點的圖,每條邊分別標識了距離。
源頂點 source = 0,初始時,
- sptSet = {false, false, false, false, false, false, false, false, false};
- distSet = {0, INF, INF, INF, INF, INF, INF, INF, INF};
將 0 包含至 sptSet 中;
- sptSet = {true, false, false, false, false, false, false, false, false};
更新 0 至其鄰接節點的距離;
- distSet = {0, 4, INF, INF, INF, INF, INF, 8, INF};
選擇不在 sptSet 中的 Min Distance 的頂點,為頂點 1,則將 1 包含至 sptSet;
- sptSet = {true, true, false, false, false, false, false, false, false};
更新 1 至其鄰接節點的距離;
- distSet = {0, 4, 12, INF, INF, INF, INF, 8, INF};
選擇不在 sptSet 中的 Min Distance 的頂點,為頂點 7,則將 7 包含至 sptSet;
- sptSet = {true, true, false, false, false, false, false, true, false};
更新 7 至其鄰接節點的距離;
- distSet = {0, 4, 12, INF, INF, INF, 9, 8, 15};
選擇不在 sptSet 中的 Min Distance 的頂點,為頂點 6,則將 6 包含至 sptSet;
- sptSet = {true, true, false, false, false, false, true, true, false};
更新 6 至其鄰接節點的距離;
- distSet = {0, 4, 12, INF, INF, 11, 9, 8, 15};
以此類推,直到遍歷結束。
- sptSet = {true, true, true, true, true, true, true, true, true};
- distSet = {0, 4, 12, 19, 21, 11, 9, 8, 14};
最終結果為源頂點 0 至所有頂點的距離:
Vertex Distance from Source 0 0 1 4 2 12 3 19 4 21 5 11 6 9 7 8 8 14
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 int[] dist = g.Dijkstra(0); 35 Console.WriteLine("Vertex\t\tDistance from Source"); 36 for (int i = 0; i < dist.Length; i++) 37 { 38 Console.WriteLine("{0}\t\t{1}", i, dist[i]); 39 } 40 41 Console.ReadKey(); 42 } 43 44 class Edge 45 { 46 public Edge(int begin, int end, int distance) 47 { 48 this.Begin = begin; 49 this.End = end; 50 this.Distance = distance; 51 } 52 53 public int Begin { get; private set; } 54 public int End { get; private set; } 55 public int Distance { get; private set; } 56 } 57 58 class Graph 59 { 60 private Dictionary<int, List<Edge>> _adjacentEdges 61 = new Dictionary<int, List<Edge>>(); 62 63 public Graph(int vertexCount) 64 { 65 this.VertexCount = vertexCount; 66 } 67 68 public int VertexCount { get; private set; } 69 70 public void AddEdge(int begin, int end, int distance) 71 { 72 if (!_adjacentEdges.ContainsKey(begin)) 73 { 74 var edges = new List<Edge>(); 75 _adjacentEdges.Add(begin, edges); 76 } 77 78 _adjacentEdges[begin].Add(new Edge(begin, end, distance)); 79 } 80 81 public int[] Dijkstra(int source) 82 { 83 // dist[i] will hold the shortest distance from source to i 84 int[] distSet = new int[VertexCount]; 85 86 // sptSet[i] will true if vertex i is included in shortest 87 // path tree or shortest distance from source to i is finalized 88 bool[] sptSet = new bool[VertexCount]; 89 90 // initialize all distances as INFINITE and stpSet[] as false 91 for (int i = 0; i < VertexCount; i++) 92 { 93 distSet[i] = int.MaxValue; 94 sptSet[i] = false; 95 } 96 97 // distance of source vertex from itself is always 0 98 distSet[source] = 0; 99 100 // find shortest path for all vertices 101 for (int i = 0; i < VertexCount - 1; i++) 102 { 103 // pick the minimum distance vertex from the set of vertices not 104 // yet processed. u is always equal to source in first iteration. 105 int u = CalculateMinDistance(distSet, sptSet); 106 107 // mark the picked vertex as processed 108 sptSet[u] = true; 109 110 // update dist value of the adjacent vertices of the picked vertex. 111 for (int v = 0; v < VertexCount; v++) 112 { 113 // update dist[v] only if is not in sptSet, there is an edge from 114 // u to v, and total weight of path from source to v through u is 115 // smaller than current value of dist[v] 116 if (!sptSet[v] 117 && distSet[u] != int.MaxValue 118 && _adjacentEdges[u].Exists(e => e.End == v)) 119 { 120 int d = _adjacentEdges[u].Single(e => e.End == v).Distance; 121 if (distSet[u] + d < distSet[v]) 122 { 123 distSet[v] = distSet[u] + d; 124 } 125 } 126 } 127 } 128 129 return distSet; 130 } 131 132 /// <summary> 133 /// A utility function to find the vertex with minimum distance value, 134 /// from the set of vertices not yet included in shortest path tree 135 /// </summary> 136 private int CalculateMinDistance(int[] distSet, bool[] sptSet) 137 { 138 int minDistance = int.MaxValue; 139 int minDistanceIndex = -1; 140 141 for (int v = 0; v < VertexCount; v++) 142 { 143 if (!sptSet[v] && distSet[v] <= minDistance) 144 { 145 minDistance = distSet[v]; 146 minDistanceIndex = v; 147 } 148 } 149 150 return minDistanceIndex; 151 } 152 } 153 } 154 }
參考資料
- 廣度優先搜索
- Dijkstra's algorithm
- Greedy Algorithms | Set 7 (Dijkstra’s shortest path algorithm)
- Bellman-Ford 單源最短路徑算法
- Introduction to Algorithms 6.006 - Lecture 16
本篇文章《Dijkstra 單源最短路徑算法》由 Dennis Gao 發表自博客園,未經作者本人同意禁止任何形式的轉載,任何自動或人為的爬蟲轉載行為均為耍流氓。