偷一份算法導論 dj 算法的僞代碼:
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)。
當僅使用線性查找算法實現 EXTRACT-MIN
時,查找的時間複雜度爲 O(V),所以很垃圾,直接導致最後結果出現 O(V^2)
,這不太好。
有些人用最小堆(優先隊列)來實現 EXTRACT-MIN
,企圖把 O(V^2)
降低到 O(VlogV)
會有一個問題:
當使用鬆弛技術時,會破壞最小堆的結構。有些人每次 EXTRACT-MIN
之前進行一次建堆操作,但這實際上又引入了 O(V)
的單次查找複雜度,比線性查找只慢不快。
所以我們需要一個算法把最小堆的結構恢復過來。這在 Q
中建立 elem
到 index
的映射,以便每次 RELAX
知道我們碰了誰,然後進行局部恢復,保持 O(logV)
的單次查找複雜度,但是這樣的缺點是需要用到優先隊列內部封裝的結構,而且維護索引的常數開銷也很大,特別是 Java PriorityQueue
這種輪子唾手可得時,我們不願意去自己寫一個最小堆(就是懶)
但是換個角度,我們可以這樣操作:
RELAX
後,立即將節點插入 Q
中。u = EXTRACT-MIN(Q)
後如果 u
是 visited
,那麼跳過它。
這樣等價於 “每次鬆弛後維護了 Q
的結構”,缺點是 Q
會變得龐大起來,每次查找是 O(logE)
,考慮到 E <= V^2
,O(logE) ~ O(logV)
,還不算太壞。循環的次數也會增多,但是如果碰到 visited == true
就會很快進入下一循環,可以認爲O(logE)
因子仍然是 O(V)
,因爲跳過循環時不進行 O(logE)
的循環內操作。