图论-单源最短路径算法(拓扑,Dijkstra,Floyd,SPFA)
前言
單源最短路徑是學習圖論算法的入門級臺階,但剛開始看的時候就蒙了,什么有環沒環,有負權沒負權,下面就來總結一下求單源最短路徑的所有算法以及其適用的情況。
單源最短路徑
設定圖中一個點為源點,求其他所有點到源點的最短路徑。
先聲明一點:有負環的圖中沒有最短路徑
因為負環繞一圈的權值和是負的,只要過一遍環,路徑就減小,可以反復過,無限減小
?
1. 無環 無負權 圖求單源最短路徑--拓撲排序
求v到其他所有點的最短路徑
歸納假設
已知v到前面n-1個點的最短路徑
求n
假設每一個點帶一個屬性sp,表示該點到源點的最短路徑長度
設第n個點為z
我們知道所有能到z的點wk,即(wk, z)是圖中的邊
這樣求出來wk.sp + len(wk, z)的最小值
就是z的sp
為什么要拓撲排序?
拓撲排序保證看n的時候,與n連接的前面的所有點都看了
復雜度O(V + E)
偽代碼:
?
2. 有環 無負權 圖求單源最短路徑--Dijkstra算法
前面拓撲排序的求所有點的最短路徑只能是無環圖
有環會導致拓撲排序無法終止
沒法用
Dijkstra算法可以解決有環 無負權最短路徑的問題
Dijkstra算法是求源點到其他所有點的最短路徑
定義兩個集合
一個集合是S, 表示已經確定了最短路徑的點
另一堆是剩下的所有點M的集合
Init-每個點的路徑長度設為無窮
每次從剩下的點中取路徑長度最小的加入到S中
開始
只有源點在S中
計算源點所有連接點的路徑長度
取出最小x的加入到S中
并對所有連接x的點更新路徑長度
再挑出最小的
反復
直到所有點都加到S中
解決了
?
注意:這里與prim算法有點像
Diljstra算法是不斷從剩下的點中選取路徑最小的加入的S中
prim算法是不斷從剩下的點中選取能到S且邊的權值最小的加入到S中
偽代碼:
const int MAX = MAX_INT; for(all w except v in G){w.sp = MAX;w.mark = false;}v.sp = 0;build a heap for all vetex;while(there exist unmarked vertex){w = heap.pop();??? //w.sp is minimumw.mark = true;for(all (w, z) in G and z is unmarked){if(w.sp + len(w, z) < z.sp){z.sp = w.sp + len(w, z);}}heapsort();}?
build the Heap? // O(VlogV)
最多需要E次更新,每一次更新O(logv)
從而總時間
O((E+V)logV)
問:為什么Dijkstra算法不能求解帶負權的路徑的圖的最短路徑?
答:采用dijkstra算法處理帶有負權邊的圖時有可能出現這樣一種情況:因為dijktra算法每一次將一個節點加入已訪問集合之中,之后不能在更新,
如圖
?
算法剛開始執行時,將A添加到集合中標記已訪問,之后選出從A到所有節點中的最短的d,于是把C加入集合中標記已訪問,之后C不能在更新了,而顯然,A與C之間最短路徑權值為0(A-B-C),發生錯誤。
?
3. 有環 有負權 圖的單源最短路徑--Floyd算法
可以用于帶負權的圖
Floyd算法
動態規劃
C[i][j] 表示i點到j點的最短距離
小問題可解-所有相鄰的邊的權值已知
大問題分解為小問題
從i到j
兩種途徑
1.圖中有邊連接i與j
2.經過k點到,C[i][j] = C[i][k] + C[k][j]
1 <= k <= N
兩種的最小值就是C[i][j]
?
定義矩陣C[N][N]
然后不斷dp
?
偽代碼:
const int MAX = MAX_INT;c[N][N]for(i = 1; i <= N; i++){for(j = 0; i <= N; j++){if((i, j) is in G ){c[i][j] = (i, j).weight;}else{c[i][j] = MAX;}}}for(k = 1; k <= N; j++){for(i = 1; i <= N; i++){for(j = 0; j <= N; j++){if(c[i][j] > c[i][k] + c[k][j]){c[i][j] = c[i][k] + c[k][j];}}}}?復雜度O(n^3)
注意k只能放在最外層的循環
不能放在最里層
若放在最里層
計算第一行eg (1, 10)時
要算(1, 5) (5, 10)
但這時(5, 10)還未知
所以確定的(1, 10)點的最短路徑是不準確 的
k放最外面的循環中
保證計算每次看計算C[i][j]時, C[i][k] + C[k][j]都是已經求過的值
?
4. 有環 有負權 圖單源最短路徑算法--SPFA
帶有負權的圖
不能使用Dijkstra算法
floyd算法又復雜度太高
然后西安交通大學acm大牛想出了SPFA
中華少年英才多啊
SPFA
設立一個隊列
d[i]表示源點到i的最短距離
init所有d[i]都為無窮
剛開始源點入隊
x出隊
檢查x連接的所有點d[i]值,如果變小,則跟新
更新了的點y,說明與y連接的點也有可能更新
y入隊
之后不斷反復
因為點數有限,所以必然一定步驟后隊列會為空
這時就求得了所有點的最短路徑
真TMD神奇
檢查每個點連接的所有點,總檢查就是邊數E
每個點可能入隊多次,設平均入隊次數為k
復雜度O(kE)
可以證明k<=2
?
Spfa 有兩種實現方法:bfs,dfs
bfs判斷起來較麻煩
得計算所有點入隊的次數
如果一個點入隊超過N次,一定有負環
bfs偽代碼:
const int MAX = MAX_INTspfa_bfs(){suppose root = s;d[N];???? //The min path length of s to ivis[N];????? //1 -- i is in the queuecount[N];???? //The count of i to enqueuefor(i = 1; i <= N; i++){d[N] = MAX;}d[s] = 0;vis[s] = 1;count[s] = 1;enqueue(s);while(!queue.empty()){x = dequeue();vis[s] = 0;for((x, y) is in G){if(d[y] > d[x] + len(x, y)){d[y] = d[x] + len(x, y);vis[y] = 1;count[y]++;if(count[y] >= N){return -1;???? // There is a minus circle}}}}}?
dfs 偽代碼:
判斷負環較快
?
vis[N];????? //1 -- i is checkedint spfa_dfs(point v){vis[v] = 1;for((v, w) is in G){if(d[w] > d[v] + len(v, w)){d[w] = d[v] + len(v, w);if(vis[w] == 0){if(spfa_dfs(w) == -1){return -1;}?????????????????????????????? //-1?? there is a minus circle}else{return -1;}}}vis[v] = 0;}總結
無環 無負權 圖單源最短路徑--拓撲排序??
有環 無負權 圖單源最短路徑--Dijkstra算法
有環 有負權 圖單源最短路徑--Floyd算法
有環 有負權 圖單源最短路徑--SPFA
這里總結了計算單源最短路徑的四種常見算法,理清楚他們分別適用的情況,以及他們之間的關系便會很好記憶。
?
總結
以上是生活随笔為你收集整理的图论-单源最短路径算法(拓扑,Dijkstra,Floyd,SPFA)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: js-sha1实现SHA1加密
- 下一篇: 四川大学计算机科学研究生,四川大学计算机