1. 程式人生 > >Floyd演算法求解每一對頂點之間的最短路徑2

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)

[i][j]就是從vi到vj的最短路徑的長度。

例項:

程式碼即解析:     

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();
	}
}

測試結果: