1. 程式人生 > >[從今天開始修煉資料結構]圖的最短路徑 —— 迪傑斯特拉演算法和弗洛伊德演算法的詳解與Java實現

[從今天開始修煉資料結構]圖的最短路徑 —— 迪傑斯特拉演算法和弗洛伊德演算法的詳解與Java實現

在網圖和非網圖中,最短路徑的含義不同。非網圖中邊上沒有權值,所謂的最短路徑,其實就是兩頂點之間經過的邊數最少的路徑;而對於網圖來說,最短路徑,是指兩頂點之間經過的邊上權值之和最少的路徑,我們稱路徑上第一個頂點是源點,最後一個頂點是終點。

我們講解兩種求最短路徑的演算法。第一種,從某個源點到其餘各頂點的最短路徑問題。

  1,迪傑斯特拉(Dijkstra)演算法

    迪傑斯特拉演算法是一個按路徑長度遞增的次序產生最短路徑的演算法,每次找到一個距離V0最短的點,不斷將這個點的鄰接點加入判斷,更新新加入的點到V0的距離,然後再找到現在距離V0最短的點,迴圈之前的步驟。有些類似之前的普里姆演算法,是針對點進行運算,貪心演算法。

    這裡我們首先約定,Vi在vertex[]中儲存的index = i,以方便闡述思路。思路如下:

    (1)我們拿到網圖,如下.我們要找到從V0到V8的最短路徑,使用鄰接矩陣儲存的圖結構。我們尋找距離V0最近的頂點,找到了鄰接點V1,距離是1,將其連入最短路徑。

 

  

  (2)修正與V1直接相關聯的所有點到V0的最短路徑。比如原本V2到V0的路徑是5,現在通過V1,將其修改為1+3 = 4.,將V4置為6,V3置為8.我們現在就需要一個數組來儲存每個點到V0的最短路徑了。我們設定一個數組ShortPathTable[numVertex]來儲存。

    同時我們還需要儲存每個點到V0的最短路徑,為此我們設定一個數組Patharc[numVertex],P[i] = k表示Vi到V0的最短路徑中,Vi的前驅是Vk。我們還需要一個數組來儲存某個頂點是否已經找到了到V0的最短路徑,為此我們設定finals[numVertex]。此時

    S[] = { 0 , 1 , 4 , 8 , 6 , I , I , I , I }
    P[] = { 0 , 0 , 1 , 1 , 1 , 0 , 0 , 0 , 0 }
    f[] = { 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 }

  (3)此時拿到與V0距離最短的點(還沒有加入最短路徑的,即final[i] = 0的點),這裡是V2,然後更新V2的鄰接點V4,V5到V0的最短路徑分別為1+3+1=5和1+3+7=11,並將V2加入最短路徑

    S[] = { 0 , 1 , 4 , 8 , 5 , I , I , I , I }
    P[] = { 0 , 0 , 1 , 1 , 2 , 0 , 0 , 0 , 0 }
    f[] = { 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 }

  

 

 

    接下來的步驟跟前面類似,我就不再重複了,大家應該也理解迪傑斯特拉演算法的步驟了,下面來看程式碼實現。

    public void ShortestPath_Dijkstra(){
        int[] ShortPathTable= new int[numVertex];
        int[] Patharc = new int[numVertex];
        int[] finals = new int[numVertex];

        //初始化
        for (int i = 0; i < numVertex; i++){
            ShortPathTable[i] = edges[0][i];    //初始化為v0的鄰接邊
            Patharc[i] = 0;
            finals[i] = 0;
        }
        finals[0] = 1;  //v0無需找自己的鄰邊

        int minIndex = 0;   //用來儲存當前已經發現的點中到V0距離最短的點
        for (int i = 1; i < numVertex; i++){
            //找到目前距離V0最近的點
            int min = INFINITY;
            for (int index = 0; index < numVertex; index++){
                if (finals[index] != 1 && ShortPathTable[index] < min){
                    minIndex = index;
                    min = ShortPathTable[index];
                }
            }
            finals[minIndex] = 1;
            //更新還未加入最短路徑的點到V0的距離
            for (int index = 0; index < numVertex; index++){
                if (finals[index] != 1 && (ShortPathTable[index] > (min + edges[minIndex][index]))){
                    ShortPathTable[index] = min + edges[minIndex][index];
                    Patharc[index] = minIndex;
                }
            }
        }
        //輸出最短路徑
        int s = 8;
        System.out.println(8);
        while ( s != 0){
            s = Patharc[s];
            System.out.println(s);
        }
    }

  其實根據這個演算法得到了v0到任意一個頂點的最短路徑和路徑長度的。時間複雜度O(n方)。

  如果我們想得到v1,或者v2等到任意一個頂點的最短路徑呢?我們要再跑一次這個演算法才能得到這樣複雜度就達到了O(n3),這是我們所不能接受的。下面我們來介紹一種直接得到每一個頂點到另外一個頂點的最短路徑。

  2,弗洛伊德(Floyd)演算法

    弗洛伊德演算法的想法是,如果V1到V2的距離,大於V1到V0再從V0到V2的距離,那麼就把V1到V0的權值的拷貝,改為V1到V0+V0到V2. 同時把儲存的最短路徑也改掉,直到網中的每一條邊都被遍歷。

    我們用二維矩陣ShortPathTable儲存行號到列號的最短路徑的權值之和(初始化為鄰接矩陣);Patharc儲存行號到列號的最短路徑中列號元素的前驅。初始化如下

    舉例如下:

 

 

     ShortPathTable矩陣:   Patharc矩陣:

 

 

     (1)從V0進入網圖中,此時沒有什麼需要變化的。

     (2)到達V1,讓所有的頂點的路徑都從V1經過,發現V0到V2的路徑大於先到V1再到V2的路徑,所以將S矩陣和P矩陣中的相應位置更改。同理S[0][3] = 8, S[0][4] = 6。

 

 

 

 

後面同理,依次迴圈V2~V8,針對每個頂點作為中轉得到計算結果。這樣我們的最短路徑就完成了。

實現程式碼如下:

    public void ShortestPath_Floyd(){
        int[][] ShortPathTable = new int[numVertex][numVertex];
        int[][] Patharc = new int[numVertex][numVertex];
        //初始化兩個矩陣
        for (int row = 0; row < numVertex; row++){
            for (int col = 0; col < numVertex; col++){
                ShortPathTable[row][col] = edges[row][col];
                Patharc[row][col] = col;
            }
        }

        for (int path = 0; path < numVertex; path++){
            for (int row = 0; row < numVertex; row++){
                for (int col = 0; col < numVertex; col++){
                    if (ShortPathTable[row][col] > (ShortPathTable[row][path] + ShortPathTable[path][col])){
                        ShortPathTable[row][col] = (ShortPathTable[row][path] + ShortPathTable[path][col]);
                        Patharc[row][col] = Patharc[row][path];
                    }
                }
            }
        }

        //列印看結果
        for (int row = 0; row < numVertex; row++) {
            for (int col = 0; col < numVertex; col++) {
                System.out.print(ShortPathTable[row][col] + "\t");
            }
            System.out.println();
        }
        System.out.println("***********************************");
        for (int row = 0; row < numVertex; row++) {
            for (int col = 0; col < numVertex; col++) {
                System.out.print(Patharc[row][col] + "\t");
            }
            System.out.println();
        }
    }

結果:

0    1    4    7    5    8    10    12    16    
1    0    3    6    4    7    9     11    15    
4    3    0    3    1    4    6     8    12    
7    6    3    0    2    5    3     5    9    
5    4    1    2    0    3    5     7    11    
8    7    4    5    3    0    7     5    9    
10   9    6    3    5    7    0     2    6    
12   11   8    5    7    5    2     0    4    
16   15   12   9    11   9    6     4    0    
***********************************
0    1    1    1    1    1    1    1    1    
0    1    2    2    2    2    2    2    2    
1    1    2    4    4    4    4    4    4    
4    4    4    3    4    4    6    6    6    
2    2    2    3    4    5    3    3    3    
4    4    4    4    4    5    7    7    7    
3    3    3    3    3    7    6    7    7    
6    6    6    6    6    5    6    7    8    
7    7    7    7    7    7    7    7    8    

我們可以發現,P矩陣的第一列與迪傑斯特拉演算法的結果是一樣的。它的時間複雜度是三次方的,但它可以解決多個頂點到多個頂點的最短路徑問題,比同樣場景下的迪傑斯特拉演算法要省時得多。

上面是兩個演算法對於無向圖的應用,實際上對於有向圖,也是同樣的使用效果。因為無向圖和有向圖的區別僅僅是鄰接矩陣是否對稱而