Floyd演算法求解每一對頂點之間的最短路徑2
已知一個各邊權值均大於 0 的帶權有向圖,對每對頂點 vi≠vj,要求求出每一對頂點之間的最短路徑和最短路徑長度。
解決方案:
1.每次以一個頂點為源點,重複執行迪傑斯特拉演算法n次。這樣,便可求得每一對頂點之間的最短路徑。總的執行時間為O(n3)。
2.形式更直接的弗洛伊德(Floyd)演算法。時間複雜度也為O(n3)。
弗洛伊德演算法思想:
假設求從頂點Vi到Vj的最短路徑。如果從Vi到Vj有弧,則從Vi到Vj存在一條長度為arcs[i][j]的路徑,該路徑不一定是最短路徑,尚需進行n次試探。
首先考慮路徑(Vi,V0,Vj)是否存在(即判別(Vi,V0)、(V0,Vj)是否存在)。如存在,則比較(Vi,Vj)和(Vi,V0,Vj)的路徑長度,取長度較短者為從
Vi到Vj 的中間頂點的序號不大於0 的最短路徑。假如在路徑上再增加一個頂點 V1,…依次類推。可同時求得各對頂點間的最短路徑。
定義一個n階方陣序列D(-1),D(0),D(1),D(2),......D(k),......D(n-1)
其中:
D(-1)[i][j]= arcs[i][j],其中arcs[i][j]為帶權值的鄰接矩陣
D(k)[i][j]=Min { D(k-1)[i][j],D(k-1)[i][k]+D(k-1)[k][j] },0≤k≤n-1
可見,D(1)[i][j]就是從vi到vj的中間頂點的序號不大於1的最短路徑的長度;
D(k)[i][j]就是從vi到vj的中間頂點的序號不大於k的最短路徑的長度;
D(n-1)
例項:
程式碼即解析:
package utils; import java.io.File; import utils.AdjacentMatrix; public class ShortestPath { public static void floydShortestPath(){ /*File file = new File("D:/xj_algorithm_test_data/shortest_path_test.txt"); AdjacentMatrix.StoreInAdjacentMatrix(file); int n = AdjacentMatrix.N; int[][] A = AdjacentMatrix.d;//A為鄰接矩陣*/ int[][] A = {//測試 {0,4,11}, {6,0,2}, {3,Global.INF,0} }; int n = A[0].length; int[][] dis = new int[n][n];//distance用來儲存dis[i][j]從vi到vj的最短距離值 //每次加入新節點k時都會比較dis[i][j]與dis[i][k]+dis[k][j]的大小以決定是否來更新最短距離 int[][] path = new int[n][n];//path[i][j]用來儲存vi到vj的最短路徑之該條路徑的vj的前驅結點 //說明: //要輸出vi到vj的最短路徑,path[i][j]儲存的最短路徑的vj的前驅結點,假設為k,即kj一定在vi到vj的最短路徑上:i->...->k->j,輸出 //k後,再只需檢視path[i][k]儲存的節點即vk的前驅結點假設為h,那麼vi到vj的最短路徑是i->...->h->k->j,以此類推, //最終可輸出vi到vj的最短路徑 for(int i =0;i<n;i++){//先做dis[][] 和path[][]的初始化工作 for(int j=0;j<n;j++){ dis[i][j] = A[i][j]; path[i][j] = i;//先假設vi到vj的直達路徑,即vj的前驅就是vi //如果i到j本來就直接可達,這麼假設沒有錯;如果i到j直接不可達,那麼後期可以通過加入其他節點而可達, //這樣dis[][]和path[][]一定會被更新,所以這裡對path[][]的假設也不無妨 //當然這樣假設的大前提:所有的節點之間都可以互相可達(可通過走其他節點) //但其實如果確實存在某兩節點不可達(路徑值無窮大),在後面的dis[][]的更新中它依然不會被更新,因為沒有中間結點使他們可達 //在最後的列印輸出中由於不滿足條件dis[i][j]!=Global.INF,path[][]裡對應的值也不會被輸出,故不會造成影響 } } for(int k =0;k<n;k++){ for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ if((dis[i][k]+dis[k][j]<dis[i][j])&&(dis[i][k]!=Global.INF)&&(dis[k][j]!=Global.INF)){ dis[i][j] = dis[i][k]+dis[k][j]; path[i][j] = path[k][j];//注意這裡不要迷惑,ij路徑上j的前驅被指為path[k][j],不要認為path[i][j]就等於了k,即 //不要誤認為此時j的前驅就是k了, //注意這裡是path[k][j]而不是k,所以path[k][j]的值有可能是k,還有可能是kj路徑上存在的其他節點h,此節點h是該路徑上j的前驅 //即以前的i->....->j變為現在的i->...->k->...->j,而不是i->...->k->j而產生擔心顧慮,後者是前者的某一種情況而已 //例如:沒加入k時有i->...->j,k->...->h->j //加入k節點後路徑i->...->k->...j更短了,那麼此時path[i][j]=path[k][j]=h } } } } for(int i=0;i<n;i++){//列印最短路徑//列印路徑i->...->j,先從j列印,再根據path[][]列印j的前驅,再列印前驅的前驅... for(int j=0;j<n;j++){ if((i!=j)&&(dis[i][j]!=Global.INF)){//dis[i][j]!=Global.INF就可以避免輸出那些相互不可達的點對的path[][]值 //因為path[][]裡即使存了資料,由於不可達,此時也不會輸出 System.out.println(); System.out.println("v"+i+"到v"+j+"的最短距離為:"+dis[i][j]); System.out.println("v"+i+"到v"+j+"的最短路徑為:"); //路徑的第一種輸出方式,k=j,ij的最短路徑先輸出k,在輸出k的前驅再輸出前驅的前驅...直到k == i,即逆向輸出 int k = j; while(k!=i){ System.out.print(k+"<-"); k=path[i][k]; } System.out.print(i); //路徑的第二種輸出方式,先輸出i->j,k1 = j的前驅,輸出i->k1->j,k1在給出前驅的前驅設為h,則輸出i->h->k->j //每次插入新加入的前驅結點構成最短路徑串 int k1 = path[i][j]; String tmpStr = "" + j; String pathStr = "" + i + "->" + j; while(k1!=i){ tmpStr = k1 + "->" + tmpStr ; pathStr = i + "->" + tmpStr ; k1=path[i][k1]; } System.out.println(); System.out.println(pathStr); //路徑的第三種輸出方式,思路與第二種完全能想同,只是數字對應的節點識別符號:如v0:A,v1:B,v2:C...... char[] charPath = {'A','B','C'}; //int pathNodesNum = 0; int k2 = path[i][j]; String tmpStr2 = "" + charPath[j]; String pathStr2 = "" + charPath[i] + "->" + charPath[j]; while(k2!=i){ tmpStr2 = charPath[k2] + "->" + tmpStr2 ; pathStr2 = charPath[i] + "->" + tmpStr2 ; k2=path[i][k2]; //pathNodesNum++; } //System.out.println(); System.out.println(pathStr2); //char[] charPath = new char[pathNodesNum]; } } } } public static void main(String[] args) { floydShortestPath(); } }
測試結果: