經典演算法研究系列:二、Dijkstra 演算法初探
三、Dijkstra 的演算法實現
Dijkstra 演算法的輸入包含了一個有權重的有向圖 G,以及G中的一個來源頂點 S。我們以 V 表示 G 中所有頂點的集合,以 E 表示G 中所有邊的集合。
(u, v) 表示從頂點 u 到 v 有路徑相連,而邊的權重則由權重函式 w: E → [0, ∞] 定義。因此,w(u, v) 就是從頂點 u 到頂點 v 的非負花費值(cost),邊的花費可以想像成兩個頂點之間的距離。
任兩點間路徑的花費值,就是該路徑上所有邊的花費值總和。
已知有 V 中有頂點 s 及 t,Dijkstra 演算法可以找到 s 到 t 的最低花費路徑(例如,最短路徑
好,咱們來看下此演算法的具體實現:
Dijkstra 演算法的實現一(維基百科):
u := Extract_Min(Q) 在頂點集合 Q 中搜索有最小的 d[u] 值的頂點 u。這個頂點被從集合 Q 中刪除並返回給使用者。
1 function Dijkstra(G, w, s)
2 for each vertex v in V[G] // 初始化
3 d[v] := infinity
4 previous[v] := undefined
5 d[s] := 0
6 S := empty set
7 Q := set of all vertices
8 while Q is not an empty set // Dijkstra演演算法主體
9 u := Extract_Min(Q)
10 S := S union {u}
11 for each edge (u,v) outgoing from u
12 if d[v] > d[u] + w(u,v) // 拓展邊(u,v)
13 d[v] := d[u] + w(u,v)
14 previous[v] := u
如果我們只對在 s 和 t 之間尋找一條最短路徑的話,我們可以在第9行新增條件如果滿足
1 s := empty sequence
2 u := t
3 while defined u
4 insert u to the beginning of S
5 u := previous[u]
現在序列 S 就是從 s 到 t 的最短路徑的頂點集.
Dijkstra 演算法的實現二(演算法導論):
DIJKSTRA(G, w, s)
1 INITIALIZE-SINGLE-SOURCE(G, s)
2 S ← Ø
3 Q ← V[G] //V*O(1)
4 while Q ≠ Ø
5 do u ← EXTRACT-MIN(Q) //EXTRACT-MIN,V*O(V),V*O(lgV)
6 S ← S ∪{u}
7 for each vertex v ∈ Adj[u]
8 do RELAX(u, v, w) //鬆弛技術,E*O(1),E*O(lgV)。
因為Dijkstra演算法總是在V-S中選擇“最輕”或“最近”的頂點插入到集合S中,所以我們說它使用了貪心策略。
(貪心演算法會在日後的博文中詳細闡述)。
二零一一年二月九日更新:
此Dijkstra 演算法的最初的時間複雜度為O(V*V+E),源點可達的話,O(V*lgV+E*lgV)=>O(E*lgV)
當是稀疏圖的情況時,E=V*V/lgV,演算法的時間複雜度可為O(V^2)。
但我們知道,若是斐波那契堆實現優先佇列的話,演算法時間複雜度,則為O(V*lgV + E)。
四、Dijkstra 演算法的執行速度
我們可以用大O符號將Dijkstra 演算法的執行時間表示為邊數 m 和頂點數 n 的函式。Dijkstra 演算法最簡單的實現方法是用一個連結串列或者陣列來儲存所有頂點的集合 Q,所以搜尋 Q 中最小元素的運算(Extract-Min(Q))只需要線性搜尋 Q 中的所有元素。這樣的話演算法的執行時間是 O(E^2)。
對於邊數少於 E^2 的稀疏圖來說,我們可以用鄰接表來更有效的實現迪科斯徹演算法。同時需要將一個二叉堆或者斐波納契堆用作優先佇列來尋找最小的頂點(Extract-Min)。
當用到二叉堆時候,演算法所需的時間為O(( V+E )logE),斐波納契堆能稍微提高一些效能,讓演算法執行時間達到O(V+ElogE)。(此處一月十六日修正。)
開放最短路徑優先(OSPF, Open Shortest Path First)演算法是迪科斯徹演算法在網路路由中的一個具體實現。
與 Dijkstra 演算法不同,Bellman-Ford演算法可用於具有負數權值邊的圖,只要圖中不存在總花費為負值且從源點 s 可達的環路即可用此演算法(如果有這樣的環路,則最短路徑不存在,因為沿環路迴圈多次即可無限制的降低總花費)。
與最短路徑問題相關最有名的一個問題是旅行商問題(Traveling salesman problem),此類問題要求找出恰好通過所有標點一次且最終回到原點的最短路徑。
然而該問題為NP-hard的;換言之,與最短路徑問題不同,旅行商問題不太可能具有多項式時間解法。如果有已知資訊可用來估計某一點到目標點的距離,則可改用A*搜尋演算法,以減小最短路徑的搜尋範圍。
二零一一年二月九日更新:
BFS、DFS、Kruskal、Prim、Dijkstra演算法時間複雜度的比較:
一般說來,我們知道,BFS,DFS演算法的時間複雜度為O(V+E),
最小生成樹演算法Kruskal、Prim演算法的時間複雜度為O(E*lgV)。
而Prim演算法若採用斐波那契堆實現的話,演算法時間複雜度為O(E+V*lgV),當|V|<<|E|時,E+V*lgV是一個較大的改進。
//|V|<<|E|,=>O(E+V*lgV) << O(E*lgV),對吧。:D
Dijkstra 演算法,斐波納契堆用作優先佇列時,演算法時間複雜度為O(V*lgV + E)。
//看到了吧,與Prim演算法採用斐波那契堆實現時,的演算法時間複雜度是一樣的。
所以我們,說,BFS、Prime、Dijkstra 演算法是有相似之處的,單從各演算法的時間複雜度比較看,就可窺之一二。
==============================================
此文,寫的實在不怎麼樣。不過,承蒙大家厚愛,此經典演算法研究系列的後續文章,個人覺得寫的還行。
所以,還請,各位可關注此算法系列的後續文章。謝謝。
二零一一年一月四日。