1. 程式人生 > >5行程式碼搞定floyd演算法

5行程式碼搞定floyd演算法

簡介

floyd是圖搜演算法中很經典的一個演算法,用於求一副圖中任意兩點之間的最短路徑(時間,花費等)。其演算法思想感覺比Dijkstra簡單,而且程式碼也很容易實現。不過就是效率比較低,三個for迴圈導致複雜度為O(n3)。

例項

假如有如下的地圖,圖中四個點代表不同的城市,帶箭頭的邊表示各城市間的航線(城市1可以飛到城市2,但城市2不可直接飛到城市1,只能通過其他城市週轉),航線附近的數字為機票價格。
這裡寫圖片描述
我們利用二維矩陣儲存這幅地圖的各個城市和每條航線的資訊。
比如1號城市到2號城市的機票為2元,則設graph[1][2]的值為2。2號城市無法到達4號城市,則設定graph[2][4]的值為∞(自己設定一個較大的值即可)。另外此處約定一個城市自己飛到自己的票價為0,例如graph[1][1]為0,具體如下:
這裡寫圖片描述


那麼如何求任意城市之間最少的機票開銷呢?
其實思想很簡單,就是通過一箇中間城市週轉一下。例如從城市1直接飛到城市3需要6元,但是可以選擇先從城市1飛到城市2(2元),再從城市2飛到城市3(3元),這樣只需要花費2+3=5元即可。
同理,從城市4直接飛到城市3機票需要12元,而可以選擇先從4飛到1(5元),再從1飛到3(6元),花費降到5+6=11元。為了省1塊錢,旅途要波折一點。
如果為了喪心病狂地省錢,不怕麻煩,甚至可以先從城市4飛到城市1,然後從1飛到2,再從2飛到3,這樣只需要5+2+3=10塊錢,比4直飛3的12塊整整省了2元。

演算法思想

所以,如果要讓任意兩個城市(例如從頂點a點到頂點b)之間的開銷變少,只能引入第三個城市(頂點k),並通過這個頂點k中轉即a->k->b,才可能縮短原來從頂點a點到頂點b的開銷。那麼這個中轉的頂點k是1~n中的哪個點呢?甚至有時候不只通過一個點,而是經過兩個點或者更多點中轉開銷則會更少,即a->k1->k2->b或者a->k1->k2…->ki->……->b。

如果現在只允許經過1號城市,求任意兩個城市之間的最低票價,應該如何求呢?只需判斷graph[i][1]+graph[1][j]是否比graph[i][j]要小即可。graph[i][j]表示的是從i號城市到j號城市之間的機票花費。graph[i][1]+graph[1][j]表示的是從i號城市先到1號城市,再從1號城市到j號城市的票價之和。其中i是1~n迴圈,j也是1~n迴圈,程式碼實現如下:

for(i = 1;i <= n;i++) {   
    for(j = 1;j <= n;j++) {   
        if ( graph[i][j] > graph[i
][1] + graph[1][j] ) graph[i][j] = graph[i][1] + graph[1][j]; } }

在只允許通過1號城市週轉的情況下,任意兩個城市之間的最低機票花費更新為:
這裡寫圖片描述
由上圖可知:在只通過1號城市中轉的情況下,3號城市到2號城市(graph[3][2])、4號城市到2號城市(graph[4][2])以及4號城市到3號城市(graph[4][3])的票價開銷都變少了。

接下來繼續求在只允許經過1和2號兩個城市中轉的情況下任意兩點之間的最低票價。此時只需要在經由1號城市週轉的結果之下,加入2號城市,再判斷如果經過2號城市是否可以使得i號城市到j號城市之間的票價變得更少。即判斷graph[i][2]+graph[2][j]是否比graph[i][j]要小,程式碼實現為如下:

//經過1號頂點   
for(i=1;i<=n;i++)   
    for(j=1;j<=n;j++)   
         if (graph[i][j] > graph[i][1]+graph[1][j])
               graph[i][j]=graph[i][1]+graph[1][j];   
//經過2號頂點   
for(i=1;i<=n;i++)   
    for(j=1;j<=n;j++)   
         if (graph[i][j] > graph[i][2]+e[2][j])  
             graph[i][j]=graph[i][2]+graph[2][j]; 

所以依次類推,只需要在最外層迴圈中,不斷的增加可以中轉的城市即可:

//多源最短路徑 floyd演算法(c/c++)
void floyd(int graph[][5],int points_num) {
        for(int k=1;k<=points_num;k++)   
            for(int i=1;i<=points_num;i++)   
                 for(int j=1;j<=points_num;j++)   
                     if(graph[i][j]>graph[i][k]+graph[k][j])   
                          graph[i][j]=graph[i][k]+graph[k][j]; 
}

完整測試程式碼

注意矩陣從(1,1)元素開始

演算法定義

define INF 999999   //定義無窮大值
#define POINTS 100    //定義城市數(頂點數)
#define EDGES 100     //定義航線數(路徑數)

//建立圖
void createGraph(int graph[][POINTS]) {
    int mid_tmp,
        points_num,
        edges_num;
    //獲取點數 & 邊數
    cout<<"請輸城市數量,航線數量"<<endl;
    scanf("%d %d",&points_num,&edges_num);

    //全部初始化為正無窮
    for (int i = 1;i <= points_num;++i)
        for (int j = 1;j <= points_num;++j) {
                if (i == j)
                    graph[i][j] = 0;
                else
                    graph[i][j] = INF;
        }

    cout<<"請輸入各城市間的航線 ,票價"<<endl;
    //輸入邊
    int m,n,length;
    for (int i = 1;i <= edges_num;++i) {
        scanf("%d %d %d",&m,&n,&length);
        graph[m][n] = length;
    }
}

//多源最短路徑 floyd演算法
void floyd(int graph[][POINTS],int points_num) {
    for(int k=1;k<=points_num;k++)   
        for(int i=1;i<=points_num;i++)   
            for(int j=1;j<=points_num;j++)   
                if(graph[i][j]>graph[i][k]+graph[k][j])   
                    graph[i][j]=graph[i][k]+graph[k][j];
}

//輸出最終的結果  
void showGraph(int graph[][POINTS],int n) {

    for(int i=1;i<=n;i++){    
        for(int j=1;j<=n;j++) {   
            printf("%10d",graph[i][j]);   
        }   
        printf("\n");   
    }
}    

主函式

int main(void) {
    int graph[POINTS][POINTS];
    createGraph(graph);
    floyd(graph,4);

    cout<<"任意城市最低票價圖:"<<endl;
    showGraph(graph,4);
    system("pause");

    return 0;
}

執行截圖

這裡寫圖片描述