Bellman-Ford
這是一種暴力求解單源最短路的方法。如果圖不存在負環,那麼任意兩點之間的最短路一定不經過相同的點。
假設 \(A\) 到 \(E\) 的最短路徑為 \(A \to B \to C \to D \to E\),那麼 \(A \to B \to C \to D\) 一定為 \(A\) 到 \(C\) 的最短路。
記 \(dis_{x}\) 表示起點 \(s\) 到 \(x\) 的最短路長度。假設當前已經有經過點數為 \(i\) 的最短路,那麼對每條邊 \(u \to v\) 都進行(記為鬆弛):\(dis_{v} = \min(dis_{v},dis_{u}+w)\) 的操作(初始 \(dis_{s}=0\),其餘點 \(dis\) 為 \(+\infty\))。這樣一定能得到點數為 \(i+1\) 的最短路。進行 \(n-1\) 次鬆弛就能算出最短路了。
如果在進行第 \(n\) 次鬆弛時,還有點的 \(dis\) 值在變小,說明存在經過點數大於 \(n\) 的最短路,即整個圖存在負環。
這種演算法的時間複雜度為 \(O(nm)\)。
SPFA
容易發現在 Bellman-Ford 中,存在許多無效的鬆弛,對於邊 \(u \to v\),當 \(dis_{u}\) 從未被更新過或者 \(u \to v\) 進行過一次鬆弛後,\(dis_{u}\) 再也沒被更新過,稱此時進行的 \(u \to v\) 的是 \(No\) 的,否則稱其是 \(Yes\) 的。
嘗試去掉這樣的無效鬆弛。維護一個佇列,裡面的點 \(u\) 進行的擴充套件(擴充套件指對一個 \(u\) 而言,\(u \to v\) 的所有鬆弛)是 \(Yes\) 的。每次取出隊頭 \(u\) 進行擴充套件,把更新成功且此時不在佇列裡的點 \(v\) 放進佇列裡。然後彈出 \(u\)。
當佇列為空時,說明所有 \(u \to v\) 的鬆弛全是 \(No\) 了,此時 \(dis_{i}\) 即為正確最短路。
如果存在負環,那麼佇列將一直非空。這樣的情況該怎麼判斷呢?記 \(Len_{i}\) 表示當前 \(s\) 到 \(i\) 的最短路經過的點數。如果松弛時存在一個點的 \(Len_{i}\) 大於 \(n\),就說明存在負環。
在一般圖上速度很快,但能被卡成 \(O(nm)\)。
Dijkstra
Dijkstra 演算法適用於非負權圖。直接介紹這個演算法的過程和證明吧。
過程:將節點分成兩個集合 \(S\) 和 \(T\),\(S\) 表示已確定最短路的點集(即 \(dis_{i}\) 一定正確的點),\(T\) 表示 \(dis_{i}\) 不一定正確的點集。每次在 \(T\) 中選出一個最小的 \(dis\) 值的點,把該點放入 \(S\) 裡,然後進行對它一次擴充套件。直到所有點都在 \(S\) 裡。最開始把所有點放入 \(T\) 裡,\(dis_{s}=0\),其餘點 \(dis\) 為 \(+\infty\)。
用堆來實現,記 \(vis_{i}\) 表示 \(i\) 是否在 \(S\) 裡,每次取出堆頂 \(x\),如果 \(vis_{x}\) 為 \(0\),對 \(x\) 做一遍擴充套件,如果 \(dis_{y}\) 成功更新就加入堆裡。然後彈出 \(x\)。用 \(vis\) 的原因是同一個點會 \(x\) 多次入堆,但是第一次取出時才進行擴充套件。
由於每個點只可能進行一次擴充套件,總共最多會鬆弛 \(m\) 次,堆的大小最多達到 \(m\),所以該演算法時間複雜度為 \(O(m \log m)\)。當圖為稠密圖時,直接暴力更優,時間複雜度為 \(O(n^2)\)。
證明:這個演算法是正確的當且僅當每次 \(T\) 中取出的最小 \(dis\) 值一定是正確的。歸納假設 \(S\) 裡的 \(p_{1},p_{2},\dots,p_{k-1}\) 裡的 \(dis\) 一定是正確的。記 \(p_{k}\) 為在 \(T\) 中 \(dis\) 最小的點。如果存在一條經過了 \(T\) 中的點的路徑的長度比 \(S\) 擴充套件而來的 \(dis_{p_{k}}\) 更小。
即 \(dis_{A} \le dis(p_{i})+w(p_{i},A)+w(B,C)+\dots+w(D,p_{k})<dis_{p_{k}}\),所以 \(dis_{A} < dis_{p_{k}}\),與 \(dis_{p_{k}}\) 在 \(T\) 中最小矛盾。故而從 \(S\) 中擴充套件而來的最小的 \(dis_{p_{k}}\) 的值一定是正確的。演算法成立。