《啊哈演算法》 第六章 最短路徑

飄過的小熊發表於2016-05-13
參考:《啊哈演算法》

Floy-Warshall

有向圖的多遠最短路徑問題

問題

求任意兩個城市之間的最短路程,也就是求任意兩個點之間的最短路徑

簡單分析:先通過條件構造出圖的二位矩陣形式,然後再通過不斷更新,在一個二維矩陣的一行中表示出橫座標點到縱座標點的最短路徑

演算法本質:假如兩個點間距離為a,若找不出其他路徑(第三點中轉)使得距離小於a,則a是他們的最小距離。

展示

#include <cstdio>
#define N 21
#define M 99999
int e[N][N];
int main() {
    int m,n;
    scanf("%d%d",&m,&n);
    int a,b,c;
    for(int i=1; i<=m; i++) {
        for(int j=1; j<=m; j++) {
            if(i==j) {
                e[i][j]=0;
            } else {
                e[i][j]=M;
            }
        }
    }
    for(int i=1; i<=n; i++) {
        scanf("%d%d%d",&a,&b,&c);
        e[a][b]=c;
    }
    //最關鍵部分,不停的中轉,中轉更新,這就是核心程式碼
    for(int k=1; k<=m; k++) {
        for(int i=1; i<=m; i++) {
            for(int j=1; j<=m; j++) {
                if(e[i][j]>e[i][k]+e[k][j]) {
                    e[i][j]=e[i][k]+e[k][j];
                }
            }
        }
    }
    for(int i=1; i<=a; i++) {
        for(int j=1; j<=a; j++) {
            printf("%3d",e[i][j]);
        }
        printf("\n");
    }
    return 0;
}

注意此演算法並不能解決負權迴路,但是能解決負權,兩者是不能混為一談的,我之前就是搞錯了。

Dijkstra 演算法,單源最短路

所謂的單源最短路

就是指定一個點,然後求出這個點到其餘各個點的最短路徑。

展示(本例是求第一個點到其餘各點的最短路):

#include <cstdio>
#define N 21
#define M 99999
int book[N];
int dis[N];
int e[N][N];
int main() {
    int m,n;
    scanf("%d%d",&m,&n);//輸入頂點數和邊數(有向圖)
    for(int i=1; i<=m; i++) {//初始化鄰接陣列
        for(int j=1; j<=m; j++) {
            if(i==j) {
                e[i][j]=0;
            } else {
                e[i][j]=M;
            }
        }
    }
    book[1]=1;
    int a,b,c;
    for(int i=1; i<=n; i++) {//輸入邊
        scanf("%d%d%d",&a,&b,&c);
        e[a][b]=c;
    }
    for(int i=1; i<=m; i++) {//初始化距離陣列
        dis[i]=e[1][i];
    }
    int u;
    for(int i=1; i<=m-1; i++) {//這個for是控制迴圈次數的,我要更新距離陣列的所有元素,但是第一個點已經不用更新了,因此剩下m-1個點,還需進行m-1次迴圈
        int mi=M;//每次都假設一個最小值
        for(int j=1; j<=m; j++) {//找出最小值,排序後再找是不現實的,因為排序的過程更改了一些元素的位置
            if(book[j]==0&&dis[j]<mi) {
                mi=dis[j];
                u=j;
            }
        }
        book[u]=1;//找到最小距離的點,表示訪問了這個點了,並使用這個點進行中轉,這個點的dis值已經是確定值,我們使用這個確定值來嘗試到達其他的點,路徑就是其他點的出邊。
        for(int k=1; k<=m; k++) {//鬆弛
            if(e[u][k]<M) {//小於M即說明它們之間存在路徑,是該點的一條出邊,等於則說明不存在,也就沒有鬆弛的必要,
                if(dis[k]>dis[u]+e[u][k]) {
                    dis[k]=dis[u]+e[u][k];//鬆弛,通過已經確定的點中轉
                }
            }
        }
    }
    for(int i=1; i<=m; i++) {
        printf("%d ",dis[i]);
    }
    printf("\n");
    return 0;
}

測試

6 9
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5 
4 3 4 
4 5 15
5 6 4

至於如何求其他路,就是變著來了,第一次先訪問哪個點,怎麼做的標記
還有使用鄰接連結串列進行優化。實在是有點沒弄懂就不實現了

Bellman-Ford——-解決負權邊

核心程式碼

 for(int k=1;k<=n-1;k++){//核心語句,好傢伙這個演算法連圖都不用存。為什麼要進行n-1輪的鬆弛。任意兩點之間的最短路徑最多包含n-1條邊
        for(int i=1;i<=m;i++){
            if(dis[v[i]]>dis[u[i]]+w[i]){//1號點到v[i]點的距離能不能通過u[i]點進行縮小
                dis[v[i]]=dis[u[i]]+w[i];
            }
        }
    }

分析

這與前兩個演算法有很大的不同,並沒有使用鄰接矩陣來儲存資料,而是直接在輸入資料中進行鬆弛,注意比較的點u,v與下輸入時的順序不一樣,還有就是為什麼要進行n-1輪鬆弛,因為任意兩點之間的最短路徑最多包含n-1條邊。同時注意到這個演算法是能解決負權邊的,同時還能顯性判斷有沒有負權邊,就是執行n-1輪迴圈結束後,仍然可以繼續鬆弛,那就一定是含有負權邊了。

展示(同樣是求1號點到其餘各點的最短路)

#include<cstdio>
#define N 21
#define M 99999
int dis[N];
int u[N];
int v[N];
int w[N];
int main()
{
    int m,n;
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++){
        dis[i]=M;
    }
    dis[1]=0;
    for(int i=1;i<=n;i++){
        scanf("%d%d%d",&u[i],&v[i],&w[i]);
    }
    for(int k=1;k<=n-1;k++){//核心語句,好傢伙這個演算法連圖都不用存。為什麼要進行n-1輪的鬆弛。任意兩點之間的最短路徑最多包含n-1條邊
        for(int i=1;i<=m;i++){
            if(dis[v[i]]>dis[u[i]]+w[i]){//1號點到v[i]點的距離能不能通過u[i]點進行縮小
                dis[v[i]]=dis[u[i]]+w[i];
            }
        }
    }
    for(int i=1;i<=n;i++){
        printf("%d ",dis[i]);
    }
    return 0;
}

測試

5 5 
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3

優化:

每次鬆弛之後,有些頂點已經求得最短路,此後這些點的估計值基本上是不會發生改變的,但是每輪鬆弛的時候還是要判斷是否需要鬆弛,浪費了時間,因此可以每次僅對最短路估計值發生了變化的頂點的所有出邊執行鬆弛操作

總結

最短路徑演算法要根據實際需求和每一種演算法的特性,選擇合適的演算法

相關文章