All Pairs Shortest Paths: Floyd-Warshall Algorithm

用途

一張有向圖,找出所有兩點之間的最短路徑。

演算法

Warshall's Algorithm」套用到最短路徑問題。

d(i, j, k) = min( d(i, k, k-1) + d(k, j, k-1), d(i, j, k-1) )
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^
                  經過第k點                    沒有經過第k點

d(i, j, k):由i點到j點的最短路徑長度。中繼點只能是第0點到第k點。

兩點之間的所有路徑,依照中繼點來分類。

一條路徑的中繼點,可能有第0點、第1點、…… 第V-1點。現在把i點到j點的路徑分成兩種:經過第V-1點、未經過第V-1點。接著兩種再各自細分為:經過第V-2點、未經過第V-2點。如此不斷細分下去直到:經過第0點、未經過第0點。

時間複雜度

時間複雜度O(V³),空間複雜度O(V²)。由於計算順序以及最小值運算的關係,記憶體得以重複使用,只需要二維陣列。

演算法:找出所有兩點之間最短路徑長度

Bottom-up Dynamic Programming,三層迴圈填表格。逐次添加一個中繼點,更新所有路徑長度。

演算法:找出所有兩點之間最短路徑

兩種記錄最短路徑的方法:紀錄中繼點、紀錄第二點。

中繼點順序

分類時,其實可以採用任意的中繼點順序。

舉例來說,把i點到j點的路線分成兩種:經過第8點、未經過第8點。接著兩種再各自細分為:經過第5點、未經過第5點。如此不斷細分下去,直到圖上每一點都用過為止。

也就是說,在k的迴圈當中,k值並非一定要從0跑到V-1。k值可以隨意變動,只要0到V-1的數字都剛好出現一次就行了。

演算法:偵測負環

除了使用Bellman-Ford Algorithm的方式以外,另外還有個更直觀的檢查方法:

d[i][j]是由i點走到j點的最短路徑長度,d[i][i]則是由i點出外繞一圈回到i點的最短路徑長度。如果d[i][i]的長度是負值,就表示這條路徑就是負環、或者包含負環(以負環作為捷徑)。

只要檢查每個環的所有可能的起點i(窮舉圖上所有點),檢查d[i][i]的長度,就可以知道圖上有沒有負環。

UVa 104 125 423 436 534 544 567 10048 10099 10246 10342 10985 10987 ICPC 4524

All Pairs Shortest Paths: Johnson's Algorithm

用途

一張有向圖,找出所有兩點之間的最短路徑。

Johnson's Algorithm首先重新調整邊的權重為非負數,順便檢查圖上是否有負環,接著放心的使用Dijkstra's Algorithm計算所有兩點之間的最短路徑。

重新調整權重

如何調整權重,才不會影響最短路徑、負環的位置呢?

這裡介紹一個巧妙的方法:首先圖上每一個x點都設定一個權重h(x),然後將一條由i點到j點的邊的權重w(i,j),重新調整成w'(i,j) = w(i,j) + h(i) - h(j)。

一、一條路徑s⤳t,權重變成了:

  w'(s⤳t)
= w'(s→a→b→…→z→t)
= w'(s,a) + w'(a,b) + … + w'(z,t)
= [ w(s,a)+h(s)-h(a) ] + [ w(a,b)+h(a)-h(b) ] + … + [ w(z,t)+h(z)-h(t) ]
= w(s,a) + w(a,b) + … + w(z,t) + h(s) - h(t)
= w(s→a→b→…→z→t) + h(s) - h(t)
= w(s⤳t) + h(s) - h(t)

一加一減相消,得到簡潔的結果。

h(s)和h(t)都是定值。由s點到t點的每一條路徑,權重的變動量都一樣,權重大小關係保持不變,於是乎由s點到t點的最短路徑保持不變。圖上的最短路徑,調整權重之後,依然保持不變。

二、一個環s⤳s,權重變成了:

  w'(s⤳s)
= w'(s→a→b→…→s)
= w(s→a→b→…→s) + h(s) - h(s)
= w(s⤳s)

環的權重保持不變,於是乎由s點到s點的負環保持不變。圖上的負環,調整權重之後,依然保持不變。

重新調整權重為非負數:利用單源最短路徑長度

調整權重而不會影響最短路徑和負環的方法已經有了。現在要妥善設定h(x)的值,讓每一條邊的權重都調整為非負數。

找到最短路徑樹之後,圖上每一條邊都不可能再做relaxation,數學式子d(i) + w(i,j) ≥ d(j),移項w(i,j) + d(i) - d(j) ≥ 0,正好就是調整權重的式子。

因此,令h(x)是由一個起點到各個x點的最短路徑長度,就能重新調整權重為非負數;無論起點是哪一點,數學式子都會成立。

起點應該選誰呢?因為圖上每一條邊都必須調整權重、圖上每一個點都必須設定h(x)值,所以起點必須能夠到達圖上每一個點,如此圖上每一個點才有h(x)值。

巧妙的解決方式:增加一個超級起點,連向圖上每一個點,權重設定為零。

順帶一提,以單源最短路徑長度重新調整權重,所有最短路徑的所有邊,權重都會變成零。這是一個很好用的特性。

演算法

一、增加一個超級起點,連向原圖每一個點,權重設定為零。
二、以超級起點作為起點,實施Bellman-Ford Algorithm。
 甲、求得超級起點到原圖每一個點的最短路徑長度,作為h(x)。
 乙、順便檢查原圖是否有負環。
三、調整原圖每一條邊(i,j)的權重:
  w'(i,j) = w(i,j) + h(i) - h(j)
四、原圖每一個點分別作為起點,
  分別實施Dijkstra's Algorithm with Priority Queue,
  找出圖上任兩點之間的最短路徑。
五、找出最短路徑後,對照原圖求出正確的最短路徑長度。
  或者利用h(x)逆推:w(s⤳t) = w'(s⤳t) - h(s) + h(t)。

時間複雜度

一次Bellman-Ford Algorithm以及V次Dijkstra's Algorithm with Priority Queue的時間。

圖的資料結構為Adjacency Lists,時間複雜度O(V² + VElogV)。當圖上的邊很少,比Floyd-Warshall來得快。

Point-to-Point Shortest Path: A* Search

用途

一張有向圖,選定一個起點與一個終點,找出起點到終點的最短路徑。

Single Source Shortest Paths的Label Setting Algorithm

實施單源最短路徑演算法,一旦遇到終點就馬上停止。比起點到終點還要長的最短路徑,得以略過計算,節省時間。

無法抵達終點的最短路徑,能不能也略過計算呢?

State Space Tree的A* Search

請見本站文件「State Space Tree」。

運用估計函數h(x),容易直奔終點,解決了方才的問題。

二維平面上的最短路徑,可以用「當前點到終點的直線距離」作為估計函數h(x)。一般的圖的最短路徑,沒有規律可循,無法估計距離。

時間複雜度

儘管Label Setting Algorithm與A* Search的時間複雜度相同,但是A* Search容易直奔終點、計算時間較少。現行的時間複雜度標記方式,無法明確區分兩者的快慢差異。

Point-to-Point Shortest Path: Label Setting Algorithm with Reweighting

估計函數用來改變遍歷順序

估計函數h(x)用來改變遍歷順序。一個估計函數,如同一個排列函數(permutation),重新排列所有點,得到一個點順序(vertex ordering)。

權重函數、成本函數,功效相同。

Label Setting Algorithm使用權重函數w(i,j)、Uniform-cost Search使用成本函數g(x),兩者的遍歷順序完全相同!

  argmin { d(s⤳i) + w(i,j) }   Label Setting Algorithm
     ʲ                         尋找不在樹上、離起點最近的點
= argmin { d(s⤳j) }
     ʲ
= argmin { g(j) }              Uniform-cost Search
     ʲ                         尋找成本最小的點

調整函數、估計函數,功效相同。

Label Setting Algorithm引入調整函數-h(x)讓權重變成w(i,j) - h(i) + h(j)、A* Search引入估計函數h(x)讓成本變成g(x) + h(x),兩者的遍歷順序完全相同!

  argmin { d'(s⤳i) + w'(i,j) }      Label Setting Algorithm with Reweighting
= argmin { d(s⤳i) - h(s) + h(i) + w(i,j) - h(i) + h(j) }
= argmin { d(s⤳i) + w(i,j) + h(j) - h(s) }
= argmin { d(s⤳j) + h(j) - h(s) }
= argmin { g(j) + h(j) - h(s) }
= argmin { g(j) + h(j) } - h(s)     h(s)是定值,不影響最小值位置
= argmin { g(j) + h(j) }            A* Search

Label Setting Algorithm with Reweighting = A* Search

我們可以堂堂正正回到圖論領域。我們可以直接使用Label Setting Algorithm的一系列演算法了!

運用調整函數-h(x),改變遍歷順序,容易直奔終點!

Point-to-Point Shortest Path: ALT Algorithm(Under Construction!)

這是個專利

此演算法是Microsoft的專利。照理來說,我不可以介紹。

https://patents.google.com/patent/US7603229B2/

說文解字

ALT是指A* Search、Landmark、Triangle Inequality。

Triangle Inequality:利用單源最短路徑長度,調整權重為非負數。
Landmark:單源最短路徑的起點。
A* Search:遍歷時容易直奔地標。

調整函數:單源最短路徑長度

d⁺(x)和-d⁻(x)都可以重新調整權重為非負數。

令d⁺(x)是由一個起點a到各個x點的最短路徑長度。最短路徑之三角不等式d⁺(i) + w(i,j) ≥ d⁺(j),移項w(i,j) + d⁺(i) - d⁺(j) ≥ 0。因此w'(i,j) = w(i,j) + d⁺(i) - d⁺(j)可以重新調整權重為非負數。

令d⁻(x)是由各個x點到一個終點a的最短路徑長度。最短路徑之三角不等式w(i,j) + d⁻(j) ≥ d⁻(i),移項w(i,j) - d⁻(i) + d⁻(j) ≥ 0。因此w'(i,j) = w(i,j) + (-d⁻(i)) - (-d⁻(j))可以重新調整權重為非負數。

估計函數:單源最短路徑長度

令d⁺(x)是由a點到各個x點的最短路徑長度。最短路徑之三角不等式d⁺(x) + d(x⤳t) ≥ d⁺(t),移項d(x⤳t) ≥ -d⁺(x) + d⁺(t)。因此h(x) = -d⁺(x) + d⁺(t)可以當作估計函數、-h(x)可以重新調整權重為非負數。

令d⁻(x)是由各個x點到a點的最短路徑長度。最短路徑之三角不等式d(x,t) + d⁻(t) ≥ d⁻(x),移項d(x,t) ≥ d⁻(x) - d⁻(t)。因此h(x) = d⁻(x) - d⁻(t)可以當作估計函數、-h(x)可以重新調整權重為非負數。

合併調整函數

兩個調整函數h₁(x)與h₂(x)一旦能夠調整權重為非負數,兩者的最大值max(h₁(x), h₂(x))也能夠調整權重為非負數。最小值亦然。

{ w(i,j) + h₁(i) - h₁(j) ≥ 0
{ w(i,j) + h₂(i) - h₂(j) ≥ 0
max( w(i,j), w(i,j) ) + max( h₁(i), h₂(i) ) - max( h₁(j), h₂(j) ) ≥ 0
w(i,j) + max( h₁(i), h₂(i) ) - max( h₁(j), h₂(j) ) ≥ 0

取最大值的好處,以估計函數的立場來看,就是估計更加精準,仍舊不高估。

按照本站行文風格,我應該講最小上界與最大下界,不過我想最大值與最小值比較好懂吧。

演算法

一、建立超級起點,調整權重為非負數。
 (調整權重為非負數,以便實施高速演算法。)
二、選擇數個地標,調整權重為非負數。
 (調整權重,即是調整遍歷順序,以便直奔地標。)
 甲、選擇數個地標。
 乙、分別計算單源最短路徑長度。
  (調整權重為非負數,以便取最大值。)
  (順帶一提,取最小值,即是一齊計算多源最短路徑。)
 丙、取最大值,合併成一個調整函數。
三、點到點最短路徑:Label Setting Algorithm系列。

選擇地標

如何選擇地標,目前還沒有定論。地標均勻分布、地標呈蜘蛛網狀散開、地標兩兩相距最遠、地標放在主幹道上宛如收費站、地標放在交通要衝四通八達之處、地標放在經常路過的地方、……。

一邊更新地標、一邊計算最短路徑,這樣又可以發幾篇論文了。

最短距離的下限與上限

估計函數h(x)負責下限,reach函數r(x)負責上限。

d(i⤳j)最短距離的下限:max( -d⁺(i) + d⁺(j), d⁻(i) - d⁻(j) )
d(i⤳j)最短距離的上限:min { max d⁻(x) + max d⁺(x) } = min { r⁻(x) + r⁺(x) }
                      max { min d⁻(x) + min d⁺(x) }

加速技巧:reach

https://www.cc.gatech.edu/~thad/6601-gradAI-fall2015/02-search-01-Astart-ALT-Reach.pdf

抵達x點的所有最短路徑的長度最大值。一旦超過最大值,就不必relaxation。計算過程當中,最短路徑長度只增不減,一旦超過最大值,甚至可以直接丟掉x點。離開x點亦然。

r⁻(x) = max d(i⤳x)
         i
r⁺(x) = max d(x⤳j)
         j

if d(s⤳i) + w(i,j) ≤ r⁻(j) and h(j⤳t) ≤ r⁺(j) then relaxtion

前後兩段路徑可能共用路徑,導致效果變差。改為窮舉穿過x點的所有最短路徑,前後兩段路徑取長度最小值。

r(x) = max { min( d(i⤳x) , d(x⤳j) ) }
      shortest path i⤳x⤳j

if min( d(s⤳i) + w(i,j) , h(j⤳t) ) ≤ r(j) then relaxtion

加速技巧:shortcut

建立便道,降低reach。

加速技巧:transit node routing

http://www.cs.au.dk/~gerth/ae16/slides/route.pdf

一塊土地的出入口們,求得所有點對最短路徑。穿越土地,直接查表。

加速技巧:highway hierarchy / highway graph

收縮必經之路。遞迴下去。

加速技巧:contraction hierarchy

收縮冷門地點,改成便道。遞迴下去。

加速技巧:hub labelling

每個點的最短路徑樹,只存一部分。起點與終點,查表找到共同中繼點。

Point-to-Point Shortest Path: Matching

有向圖演算法

請先具備「Matching」之知識。

利用最小權二分匹配,解決有向圖的點到點最短路徑問題。但是圖上不得有負環。

一、額外建立二分圖:
 點:X側、Y側都有V個點。
 邊:若原圖有一條i點到j點的有向邊,
   則二分圖就有一條Xi點到Yj點的邊。
二、轉化問題:
 口、增加Xi點到Yi點的邊,權重設為零。
 口、X側,去掉起點,也去掉連至起點的邊。
 口、Y側,去掉終點,去掉終點連出的邊。
三、計算最小權二分匹配。
一、額外建立二分圖:
 口、複製原圖的adjacency matrix。
二、轉化問題:
 口、對角線改為零。
 口、去除起點編號的column。
 口、去除終點編號的row。
   (最後成為(V-1)×(V-1)矩陣。)
三、計算最小權二分匹配。

無向圖演算法

利用最小權匹配,解決無向圖的點到點最短路徑問題。但是圖上不得有負環。

一、額外建立無向圖:
 點:V個原來的點,再加上V個複製的點。
 邊:若原圖有一條i點到j點的無向邊,
   則新圖就有:
   一條i 點到j 點的無向邊、
   一條i'點到j 點的無向邊、
   一條i 點到j'點的無向邊、
   一條i'點到j'點的無向邊。
二、轉化問題:
 口、增加i點到i'點的無向邊,權重設為零。
 口、去掉起點,也去掉連著該點的邊。
 口、去掉終點的複製點,也去掉連著該點的邊。
三、計算最小權匹配。