Shortest Path in class
Lecture by zolution Credit by TreapKing
2018/06/16
Brainstorming Brainstorming
• 給定一個有權重的無向圖
你要如何把它轉成另一個無向圖,讓他可以使用 BFS 解決最短路 呢?
Floyd-Warshall Floyd-Warshall
• 感覺對對的,但總有種不踏實感
• 為什麼說這個算法是某種 DP 呢?
我大 DP 我大 DP
• 原本的狀態:
• d[i][j]: 從點 i 走到點 j 的最短距離
• for k = 1..n
• for i = 1..n
• for j = 1..n
• d[i][j] = min(d[i][j], d[i][k]+d[k][j]);
我大 DP 我大 DP
• 改個狀態:
• d[k][i][j]: 從點 i 走到點 j 的最短距離,且除了 i 跟 j 以外,只能經過前 k 個點
• for k = 1..n
• for i = 1..n
• for j = 1..n
• d[k+1][i][j] = min(d[k][i][j], d[k][i][k+1]
+d[k][k+1][j]);
Dijkstra Dijkstra
為什麼他是對的?
Dijkstra Dijkstra
先說,不能有負邊
Dijkstra Dijkstra
那為什麼不能有負邊
Dijkstra Dijkstra
O(E+V 2 )
還可以更快嗎?
Dijkstra Dijkstra
Dijkstra Dijkstra
Dijkstra Dijkstra
• 從一個集合裡面找出最小的數字
• priority_queue(heap)!!
• 用 priority_queue 找到 d[] 最小的點,然後用它來更新其他點 的 d[]
• 原本的做法: (V 輪 ) * ((O(V) 找到 d[] 最小的點 ) + ( 更新 連到它的點 )) = O(E+V2)
• 新做法: (V 輪 ) * ((O(logV) 找到 d[] 最小的點 ) + ( 更新連 到他的點 ) ,把更新後的點放進 heap) = O((V+E)logV)
Dijkstra Dijkstra
怎麼找出最短路徑本身?
最短路徑樹 最短路徑樹
• 每一個最短路徑一定都是簡單路徑
• 如果起點到每個點的最短路徑都唯一的話,那把這些路徑疊起來 會變成一棵樹
• 我們稱這種樹叫做最短路徑樹
• 如果最短路徑們不唯一的話,一樣會存在一顆最短路徑樹,只是 那棵樹不唯一
最短路徑樹 最短路徑樹
• 每一個最短路徑一定都是簡單路徑
• 如果起點到每個點的最短路徑都唯一的話,那把這些路徑疊起來 會變成一棵樹
• 我們稱這種樹叫做最短路徑樹
• 如果最短路徑們不唯一的話,一樣會存在一顆最短路徑樹,只是 那棵樹不唯一
• 樹上的節點,父節點是唯一的
• 紀錄 predecessor!
Bellman-Ford Bellman-Ford
Bellman-Ford Bellman-Ford
• 設 d[i] 是從起點走到點 i 的最短距離
• 根據定義: d[j] <= d[i]+e(i, j) ,否則 d[j] 就不是最短距離
• 小性質:只要對於所有的 (i, j) 都滿足 d[j] <= d[i]+e(i, j)
,而且對每個 j 都存在 d[j] = d[i]+e(i, j) ,那麼 d[] 就是 最短距離陣列
• 想法:如果找到一組 (i, j) 滿足 d[j] > d[i]+e(i, j) ,就把 d [j] 更新成 d[i]+e(i, j)
• 要怎麼更新?
• 亂更新!!
Bellman-Ford Bellman-Ford
Bellman-Ford Bellman-Ford
• 每次都跑完整個盤面怎麼感覺笨笨的
• 如果圖很大的話,每次就花一堆時間在更新後面的 inf 們
• 可不可以每次只跑那些有被更新到的點?
• SPFA(Shortest Path Faster Algorithm)!!
SPFASPFA
• 1. 一開始把起點 push 到 queue 裡面
• 2. 從 queue 裡面 pop 出一個點 u
• 3. 用 u 去放鬆 ( 更新 ) 其他點
• 4. 把所有被更新到的點都 push 到 queue 裡面
• 5. 如果 queue 已經空了,那麼算法結束。否則回到 2.
SPFASPFA
• 這個算法感覺聰明多了!
• 我們來看看它的時間複雜度進步了多少吧!
• O(VE)
• 足夠慘的時候,還是會跑到 VE 的
• 不過有些人說這東西期望上大概是 O(kE) , k 大概是 2
• 實務上其實挺快的,競賽的話就看出題者有沒有特別卡 ( 其實不 太好卡 )
怎麼卡 SPFA 怎麼卡 SPFA
• SPFA 感覺快快的,該怎麼卡呢?
• 生一個 V=sqrt(E) 的完全圖
• for i = 1..(n-1)
• e[i][i+1] = 1
• for i = 1..(n-1)
• for j = 1..n \ {i+1}
• e[i][j] = 10*n-2*i
• 這樣可以讓 SPFA 跑成大概 E*sqrt(E)
• 不知道有沒有別的卡法,而且好像也不一定卡得掉
小總結小總結
• Bellman-Ford :亂更新一通
• SPFA :好好寫的 Bellman-Ford
• Dijkstra :如果沒有負邊的話,利用這個好性質來更好的更新其 他點
各種比較各種比較
算法 Floyd-Warshall Bellman-Ford SPFA Dijkstra
時間複雜度 O(V3) O(VE) O(VE) (期望 O(kE)) O(V2) or O(ElogV)
處理負邊 O O O X
處理負環 X O O X
特點 好寫 沒用 可以處理負環 最快
負環負環
• 什麼叫做處理負環呢?
• 不是說好有負環的話就沒有最短路嗎?
• 可以拿來看有沒有負環
• 例如更新的次數太多
• 但沒負環一定沒有解嗎?
負環負環
負環負環
• 前面提到的可以處理負環的演算法都可以處理上一頁的那種情況
• 那要怎麼分辨一個有負環的圖存不存在一個 s-t 最短路呢?
• 更新太多次 有負環
• 如果有負環的話,從終點回朔回去,看有沒有經過重複的點
• 如果走到了重複的點,那就表示有負環
• 順帶一提,如果只是要判斷有沒有負環的話, Floyd-Warshall 也可以判斷有沒有負環,而且只要看一下有沒有 d[i][i]<0 就好