前言
算法中的最短路徑問題,是一個經典的算法問題。旨在尋找圖(由頂點和邊組成)中頂點到頂點間的最短路徑。而我們接下來的幾篇文章就會一起學習最短路徑問題中常用的Floyed-Warshall、Dijkstra、Bellman-Ford以及Bellman-Ford的隊列優化這四種算法。這些最短路徑的算法在實際運用中也有不可忽視的作用。接下來,我們就來學習一下Floyed-Warshall算法。
具體問題
在前面幾篇文章中,我們好不容易從迷宮中利用廣度優先搜索和深度優先搜索找到了女朋友。準備帶她出門旅遊一趟,放鬆一下,畢竟困在迷宮中的她也嚇壞了。比如,你們計劃去 A、B、C、D四個城市,兩個城市之間的路途是不同的,如下圖所示
上圖中的A、B、C、D表示四座城市,其中從A到C的箭頭表示,A城市到C城市的距離是6,而C到A的箭頭表示,C城市到A城市的距離是7。如果兩個城市之間只有一條線嗎,就表示是單邊的路,並沒有回去的路線。現在我們需要知道任意兩個城市之間最短的路程,比如A到C的路程有以下幾條。
Floyd-Warshall算法
這個圖的意思是:
A到A,標註爲0,就是這個城市到自己爲0
B到A標註爲無窮的符號,意思就是B到A沒有之間的道路,可能需要通過其他的城市中轉,
A到B,標註爲2,意思是從A之間到B的路徑長度是2。其他的同理
這樣一來,我們就通過一個簡單的圖表,就把問題具體化了,我們就可以通過一個二維數組來表示任意兩點之間的路徑。如果我們有需要改變的項,之間更新這個二維數組就可以了。最後我們通過這個二維數組就可以輕鬆的得出任意兩點之間的最短路徑。
思考過程
上面的數組是最初的狀態,並沒有藉助其他的城市作爲中轉。那如何計算允許藉助所有城市進行中轉的任意兩個城市的最短路徑呢。首先
,我們考慮如果只允許藉助A城市進行中轉的話,我們應該怎麼樣寫代碼呢?(在數組中,我們用4表示城市D,用3表示城市C,2表示城市B
,1表示 城市A,paths[A][A] = 0表示A城市到A城市距離是0,paths[B][A] = 無窮(我們可以用99999表示無窮))
比如,我們要計算D到C城市的最短路徑就是
paths[4][3] = 12;
如果只允許通過A城市中轉,那麼用A進行中轉的結果就是
paths[4][1]+ paths[1][3] = 5 + 6;
這樣的話,其實我們就可以編寫如下代碼:
if (paths[4][3] > paths[4][1]+paths[1][3]){
paths[4][3] = paths[4][1]+paths[1][3];
}
那麼如果要計算任意兩點,在只允許通過A城市中轉的情況下的,最短路徑其實就可以寫出下面的代碼:
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (paths[i][j] > paths[i][1]+paths[1][j]){
paths[i][j] = paths[i][1]+paths[1][j];
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (paths[i][j] > paths[i][2]+paths[2][j]){
paths[i][j] = paths[i][2]+paths[2][j];
}
}
}
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (paths[i][j] > paths[i][k]+paths[k][j]){
paths[i][j] = paths[i][k]+paths[k][j];
}
}
}
}
初始化以及調用代碼
public void shortestPath(){
//初始化城市地圖
int[][] paths = new int[5][5];//因爲我們後面爲了好理解,是從paths[1]開始,paths[4]結束的,所以這裏初始化爲5*5的數組
/**0表示它自己到他自己,999999這裏用來表示無窮遠,意思就是沒有之間的線路相通*/
paths[1][1] = 0; paths[1][2] = 2;paths[1][3] = 6;paths[1][4] = 4;
paths[2][1] = 999999;paths[2][2] = 0;paths[2][3] = 3;paths[2][4] = 999999;
paths[3][1] = 7; paths[3][2] = 999999; paths[3][3] = 0;paths[3][4] = 1;
paths[4][1] = 5; paths[4][2] = 999999; paths[4][3] = 12;paths[4][4] = 0;
floydWarshall(paths,4);
}
floyd算法核心代碼 /**
* 最短路徑
* Floyd-Warshall算法
* 計算四個城市中,任意兩個城市間的最短距離
*
* @param paths 傳遞過來的是,城市和路徑的二維數組
* paths[1][3] = a,表示的就是,1號城市到3號城市的最短路徑是a
* @param n 表示的是城市的數量
* */
public void floydWarshall(int[][] paths,int n){
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (paths[i][j] > paths[i][k]+paths[k][j]){
paths[i][j] = paths[i][k]+paths[k][j];
}
}
}
}
//我們排列完成了,現在來打印一下這個數組
StringBuilder buil = new StringBuilder();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
buil.append(" "+paths[j][i]);
}
buil.append("\n");
}
Log.i("hero","---排列結果===\n"+buil.toString());
}
運行結果從結果上看,其實已經吧通過多個城市中轉的情況也考慮進行了,其實是因爲這個路徑數組一直是動態變化的,什麼意思呢?
第二次最外層循環結束的時候,數組表示,允許通過B中轉情況下,雖然最初的時候,D到B沒有之間的路聯通,但是第一次循環結束的時
候,已經藉助A點進行了聯通,現在的D到B,其實距離就是D到A再到B = 7,所以現在D到B到C的距離就是7 + 3 = 10。
那麼如果第一次循環D到B也沒有聯通呢?,下面的循環就會繼續嘗試去聯通他們,直到所有的點都嘗試一遍之後,如果還是沒法聯通,
說明他們兩個怎麼樣也不能到達。
小結
上面就是floyd算法,通過這種算法,我們可以求出任意兩點間的最短路徑,它的時間複雜度是O(N * N * N),他的寫法是如此的簡單,
簡單到讓人難以置信,所以在對時間要求不高的情況下,我們完全可以使用這種算法,當然,該算法不能解決帶有“負權迴路”的問題,如圖
如果出現了這樣的迴路,每次A—》B—》C,路徑就會2 + 3 + (-6) = -1,這樣就會永遠也找不到最短路徑,因爲每次經過這個迴路,
路徑都會-1。
總結
上面我們就深入學習了最短路徑的第一種算法Floyd-Warshall算法。它的核心代碼相當簡單,而且原理也非常容易理解。它是求任意兩
點間的最短路徑的非常容易學習的一種算法。那麼如果我們要求解某一點到其他各點的最短路徑,我們應該怎麼做呢?如果用floyd算法其實也
是可以達到目的,只需要把結果中所有源點出發的列舉出來就行,但是下一篇文章,我們會去學習求解單源最短路徑的一種算法Dijkstra算法。
因個人水平有限,難免有錯誤和不準確之處,請大家指正批評。
算法的學習永不止境,我們纔剛剛開始而已,繼續加油喲。