給定一個帶權圖及點 l, r
,求點 l
走到點 r
的最小路徑權重和。
設一圖:
其中要求第 點和第 點的最短路徑,那麼答案就是:
shortest path
一開始時,大家想出的是複雜度較高的算法,但都是後來複雜度較低演算法的基礎。
其中一個大概念是鬆弛 (Relaxation
)
設有一圖:
我們可以得知:從第 點到第 點,如果只走一條路,則長度為 。
但若找到一點 ,使 ,則答案會縮小,更接近最短路徑解。如下圖例子:
此技巧即鬆弛,且我們能得知: (因為鬆弛是可以進行很多次的)
這個演算法就是將所有點對其他點都鬆弛過,那麼找到的路徑解即最短路徑。
偽代碼
Time Complexity:
為甚麼迴圈的順序是 k, i, j
呢?
看看以下的圖:
假設迴圈擺放是 i, j, k
,那麼 dp[i][j]
只會遍歷「連續的 k
次」
而不管是第 點都無法鬆弛 dp[1][2]
(因為 dp[1][2]
是最先被鬆弛的)
這樣就會導致 dp[1][2]
有問題。
因此想法應該是對每個點窮舉所有 dp[i][j]
鬆弛。
優點:經過一次 Floyd-Warshall
後,所有兩點最短距離就都知道了!
由 Floyd-Warshall
程式碼可知它是對整個圖中的每一點鬆弛。
但我們可以先得知起始點,並對「邊」進行鬆弛。
一次只能跑出一個點對所有點的最短路徑,dp[i]
代表一點 u
走到 i
點的最短距離
由於一條最短路最多會經過 條邊,因此只要對每個邊鬆弛 次就好。
因此又誕生一個演算法:Bellman-Ford
偽代碼
而觀察以下圖:
從第 點到第 點的最短路徑是 還是 還是 …呢?
由於這種圖(負環)沒有最短路徑,需排除。
而負環會鬆弛超過 次,因此可以在 Bellman-Ford
演算法結束後判斷。
Time Complexity:
這時,有比較快的演算法出現。
從 Bellman-Ford
可得知,它是對每個邊都鬆弛。
然而,在求兩點距離時,一個點被鬆弛過,才可以去鬆弛其他點。
而可鬆弛的點會從原點開始擴散。
這時實作及概念會有點像 BFS
。
就是從起點 i
擴散,到達每一點 k
時一樣要判斷 dis[k] = min(dis[j] + dis(j, k), dis[k])
而因為它是 Bellman-Ford
演算法的優化版,因此它也可以檢查負環。
幾個實作上的新東西:
check[]
判斷點不在 queue
內才可以做 BFS
(不然用 queue
裡的點做 BFS
就好)cnt[]
判斷點被丟進 queue
幾次,用來檢查負環struct road
剩下 r, val
,因為 l
可以用 vector
的下標搞定Time Complexity:
但有很大的常數優化 (剪枝)
最後,還有一個幾乎是最優的演算法。
但它無法檢查負環,也不能解有負環的題目。
不過…要檢查負環的題目數量趨近於
想法:從起點延伸出去的邊中,先把所有邊都丟進一個容器裡(權重設定為 dis(start, i) + v
),
再選出容器中權重最小的邊,把它接起來,此時權重即為連接到的 j
點權重。
接著以 j
為原點擴散,把連接到的邊丟進容器,重複動作直到連接至終點。
為了讓容器有「選出最小邊」的功能,因此選用 priority_queue
Time Complexity:
這題是「兩點最短路」的變化題,因為可能不只一個避難所。
可以把所有避難所的 dis[i]
都先設為 ,
而因為沒有負環,所以用 dijkstra
就好。
code
其實…記 dijkstra
就好,因為其他真的太少用到了
有餘力再去記其他演算法