在網圖和非網圖中,最短路徑的含義不同。非網圖中邊上沒有權值,所謂的最短路徑,其實就是兩頂點之間經過的邊數最少的路徑;而對於網圖來說,最短路徑,是指兩頂點之間經過的邊上權值之和最少的路徑,我們稱路徑上第一個頂點是源點,最后一個頂點是終點。
我們講解兩種求最短路徑的算法。第一種,從某個源點到其余各頂點的最短路徑問題。
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矩陣的第一列與迪傑斯特拉算法的結果是一樣的。它的時間復雜度是三次方的,但它可以解決多個頂點到多個頂點的最短路徑問題,比同樣場景下的迪傑斯特拉算法要省時得多。
上面是兩個算法對於無向圖的應用,實際上對於有向圖,也是同樣的使用效果。因為無向圖和有向圖的區別僅僅是鄰接矩陣是否對稱而已。