概述
Floyd算法又稱爲插點法,是一種利用動態規劃的思想尋找給定的加權圖中多源點之間最短路徑的算法,與Dijkstra算法類似。該算法是一種在具有正或負邊緣權重(但沒有負環)的加權圖中找到最短路徑的算法,即支持負權值但不支持負權環。弗洛伊德算法採用的是動態規劃思想,其狀態轉移方程如下:
其中matrix[i,j]表示i到j的最短距離,k是窮舉i到j之間可能經過的中間點,當中間點爲k時,對整個矩陣即從i到j的路徑長度進行更新,對所有可能經過的中間點進行遍歷以得到全局最優的最短路徑。算法的單個執行將找到所有頂點對之間的最短路徑長度,與迪傑斯特阿拉算法的計算目標有一些差異,迪傑斯特拉計算的是單源最短路徑,而弗洛伊德計算的是多源最短路徑,其時間複雜度爲O(n³)。雖然它不返回路徑本身的細節,但是可以通過對算法的簡單修改來重建路徑,我們利用這個思想,通過遞歸的方式訪問每條路徑經過的中間節點,對最終的路徑進行輸出。
算法描述
在有向圖 G=(V,E) 中,假設每條邊 E[i] 的長度爲 w[i],找到V鍾任意兩點之間的路徑長度最小值。
算法流程
本節將對算法流程進行模擬,設置Graph爲包含7個頂點和9條邊的有向無環圖,Graph如下:
弗洛伊德算法選取某個節點k作爲i到j需要經過的中間節點,通過比較d(i,k)+d(k,j)和現有d(i,j)的大小,將較小值更新爲路徑長度,對k節點的選取進行遍歷,以得到在經過所有節點時i到j的最短路徑長度,通過不斷加入中間點的方式更新最短路徑。同時在path數組中存儲i到j所經過的中間節點k,用於最後遞歸調用輸出路徑結果。
算法步驟如下:
1、初始化矩陣。
2、選取0號節點作爲中間點,初始化矩陣。
3、選取1號節點作爲中間點,更新矩陣,通過兩層循環計算(i->1),(1->j)的路徑是否比目前i到j的路徑長度更短。此時可以將矩陣數值看作是將0、1作爲中間點獲得的多源最短路徑長度。
4、選取2號節點作爲中間點,更新矩陣,通過兩層循環計算(i->2),(2->j)的路徑是否比目前i到j的路徑長度更短。此時可以將矩陣數值看作是將0、1、2作爲中間點獲得的多源最短路徑長度。
5、選取3號節點作爲中間點,更新矩陣,通過兩層循環計算(i->3),(1->3)的路徑是否比目前i到j的路徑長度更短。此時可以將矩陣數值看作是將0、1、2、3爲中間點獲得的多源最短路徑長度。
6、選取4號節點作爲中間點,更新矩陣,通過兩層循環計算(i->4),(4->j)的路徑是否比目前i到j的路徑長度更短。此時可以將矩陣數值看作是將0、1、2、3、4作爲中間點獲得的多源最短路徑長度。
7、選取5號節點作爲中間點,更新矩陣,通過兩層循環計算(i->5),(5->j)的路徑是否比目前i到j的路徑長度更短。此時可以將矩陣數值看作是將0、1、2、3、4、5作爲中間點獲得的多源最短路徑長度。
8、選取6號節點作爲中間點,更新矩陣,通過兩層循環計算(i->6),(6->j)的路徑是否比目前i到j的路徑長度更短。此時可以將矩陣數值看作是將所有節點作爲中間點獲得的多源最短路徑長度,遍歷結束,得到最後結果。
算法實現
public class FloydAlgorithm {
public static int MaxValue = 100000;
public static int[][] path;
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("請輸入頂點數和邊數:");
//頂點數
int vertex = input.nextInt();
//邊數
int edge = input.nextInt();
int[][] matrix = new int[vertex][vertex];
//初始化鄰接矩陣
for (int i = 0; i < vertex; i++) {
for (int j = 0; j < vertex; j++) {
matrix[i][j] = MaxValue;
}
}
//初始化路徑數組
path = new int[matrix.length][matrix.length];
//初始化邊權值
for (int i = 0; i < edge; i++) {
System.out.println("請輸入第" + (i + 1) + "條邊與其權值:");
int source = input.nextInt();
int target = input.nextInt();
int weight = input.nextInt();
matrix[source][target] = weight;
}
//調用算法計算最短路徑
floyd(matrix);
}
//非遞歸實現
public static void floyd(int[][] matrix) {
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++) {
path[i][j] = -1;
}
}
for (int m = 0; m < matrix.length; m++) {
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++) {
if (matrix[i][m] + matrix[m][j] < matrix[i][j]) {
matrix[i][j] = matrix[i][m] + matrix[m][j];
//記錄經由哪個點到達
path[i][j] = m;
}
}
}
}
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++) {
if (i != j) {
if (matrix[i][j] == MaxValue) {
System.out.println(i + "到" + j + "不可達");
} else {
System.out.print(i + "到" + j + "的最短路徑長度是:" + matrix[i][j]);
System.out.print("最短路徑爲:" + i + "->");
findPath(i, j);
System.out.println(j);
}
}
}
}
}
//遞歸尋找路徑
public static void findPath(int i, int j) {
int m = path[i][j];
if (m == -1) {
return;
}
findPath(i, m);
System.out.print(m + "->");
findPath(m, j);
}
}
樣例輸入:
7 10
0 1 6
1 2 5
0 3 2
3 1 7
3 4 5
1 2 5
1 5 3
5 2 3
5 4 2
4 6 1
Q&A
問:算法中的三層for循環,每一層分別控制什麼?
答:第一層循環設置中間點k,第二層循環設置起始點i,第三層循環設置結束點j。
問:爲什麼弗洛伊德算法支持負權值?
答:因爲路徑更新是根據新值和舊值比較獲得的,最終的結果都是在最後一次迭代過程中對全局進行更新而得到的,中間的每次迭代只是一次局部調整而非最終結果。而不像迪傑斯特拉採用的貪心策略,每一次迭代都確定出一條最短路徑,負權的出現使得不能保證每次迭代都是最優解。