1. 程式人生 > >2018.3.10 bellman-ford algorithm, floyd-warshall algorithm and johnson's algorithm

2018.3.10 bellman-ford algorithm, floyd-warshall algorithm and johnson's algorithm

需要 for 出現 man LG 循環 基礎上 問題: 是我

這周還是繼續dynamic programming,不過回到了圖算法,因為之前講圖算法的時候還沒有講到動態規劃,而這三個算法要麽本身是動態規劃算法,要麽要用到動態規劃算法。

1.bellman-ford是一種求無負權值的環路的有向圖兩點間最小路徑的算法,也就是說和dijkstra解決的是同一個問題。但是dijkstra無法計算有負權值的邊時的情況,bellman-ford可以處理這種情況。如果給定的圖有負權值的環路的話,那麽就會出現負無窮大的最小路徑,那當然是處理不了的,不過如果只有正權值的環路,那麽bellman-ford是可以處理的。bellman-ford的核心trick是:從給定源點,到任何一點的最小路徑,它的邊數不應該超過n-1條。因為超過了的話就意味著至少經過了某個點兩遍,那就意味著有負權值的環路,那麽就不對了。

那麽在知道了這個trick之後,我們就只需要做n-1次循環就可以了:初始化除源點以外的每個結點到源點的最小路徑為正無窮大,然後每次循環,我們從源點向外延伸一條邊,更新一次能延伸到的所有結點的最小路徑。做n-1次循環後,得到的所有點的最小路徑,就是我們要得到的結果。不過如果有負權值的環路的話,這個算法是不對的,但是也沒關系,我們只需要再做一次這個循環,再延伸一條邊,如果各點相對源點的最小路徑有變化(也就是說有減小),那麽根據某個我沒看的證明(-_-!),這時就出現了負權值環路了,那麽雖然算法不對,至少我們也檢測到了負權值環路,知道了問題不可解。如果再做一次循環之後各點相對源點的最小路徑沒有變化,那就意味著沒有負權值環路,那麽得到的就是正確結果。

bellman-ford是O(mn)的,比dijkstra的O(mlg(n))慢不少,不過適用範圍是更廣的,而且據說還能分布式計算。

2.如果要計算一個給定有向圖中,任意兩點之間的最小路徑,就是說每兩點的組合都要求最小路徑,那麽很簡單,跑n次bellman-ford就行了。不過這樣子非常慢。floyd-warshall會更快一些的解決這個問題。floyd-warshall是一個很標準的dynamic programming算法。它把所有結點標記出編號1~n,那麽從點i到點j的最小路徑怎麽求呢,我們像一般的動態規劃的思路一樣,把它變成subproblem,方法是從點i到點j的路徑,只允許經過編號1~k-1的點,那麽相對1~k,就是一個subproblem吧。

實際上這很像最早講動態規劃時舉的max weight independent set的栗子,從up到bottom的過程中,每個subproblem相比上一個subproblem,只是多了一個編號為k的點是不是在路徑中的選擇。也就是說,這裏是非常經典的兩種方案的動態規劃模型。對於從i到j,只允許經過1~k-1的最短路徑的問題:如果k不在路徑中,那麽問題等同;如果k在路徑中,那麽把問題拆分為 “從i到k 加上 從k到j” 就解決了。

最後的算法是k,i,j從1到n的三層循環。復雜度為O(n^3),比跑n次bellman-ford的O(mn^2)要好,因為對於密集圖,m最大是可以有n^2的。所以越密集的圖,floyd-warshall的優勢越大。

3.johnson‘s algorithm是解決什麽問題呢,和floyd-warshall一樣,也是解決給定圖所有點組合間最小路徑的問題的。我們之前說解決這個問題可以跑n次bellman-ford,雖然比較慢。johnson屌在哪呢,他實際上是跑了n次dijkstra來解決這個問題,而且這算法居然還能用在有負權值的邊的情況!

trick在於,這算法是先用一種酷炫的方法把有負邊的原圖轉化為沒有負邊的新圖,然後對新圖跑n邊dijkstra,然後再把得到的新圖的最短路徑轉化為原圖的。

這裏這種把有負邊的原圖轉化為沒有負邊的新圖的辦法,叫做reweight technique。這個辦法是:在原圖基礎上,加一個新結點,把新結點與原圖的每個結點都用一條權值為0的邊連通,然後以新結點為源點,對原圖的各結點求最小路徑——很顯然,就是跑一遍bellman-ford。得到的每個最小路徑,就是原圖該結點的weight。這個weight能幹啥呢:比如說原圖有一條邊,是從i到j,權值為負,那我們把權值加上i的weight,減去j的weight,得到的新權值,根據某個我沒看的證明(-_-!!),這個新權值一定是正的。對原圖所有的變都做這樣的操作,那麽就得到了一個所有邊權值都為正的新圖。

這個方法真的很吊,因為按這樣去轉化,任意兩點之間的路徑,其實只和原路徑,以及這兩個點的weight有關,和其他點的weight都沒有關系,這樣子後面往回轉化的時候也非常好轉化。

然後就是對新圖n遍dijkstra了。得到的最短路徑要變回去,這次減去i的weight,加上j的weight,就是原圖的最短路徑了。這個算法相當於1遍bellman-ford加n遍dijkstra,最後是O(mn lg(n))的,可以說是很快了。

2018.3.10 bellman-ford algorithm, floyd-warshall algorithm and johnson's algorithm