圖論-單源最短路徑算法(拓撲,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)


僞代碼:

const int MAX = MAX_INT
for(all w){

    w.sp = MAX

}

init w.indegree DFS

w with 0 indegree, enqueue


while(!queue.empty()){

    w = dequeue();

    for(all (w, z)){

        if(w.sp + len(w, z) < z.sp){

            z.sp = w.sp + len(w, z);

        }

        z.indegree--;

        if(z.indegree = 0){

            z.enqueue();

        }

    }

}


 

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 minimum

    w.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_INT

spfa_bfs(){

    suppose root = s;

    d[N];     //The min path length of s to i

    vis[N];      //1 -- i is in the queue

    count[N];     //The count of i to enqueue

    for(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 checked

int 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

這裏總結了計算單源最短路徑的四種常見算法,理清楚他們分別適用的情況,以及他們之間的關係便會很好記憶。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章