Floyd最短路算法(全局/多源最短路)
求任意兩個點之間的最短路徑。這個問題這也被稱爲“多源最短路徑”問題。
Floyd算法是一個經典的動態規劃算法。是解決任意兩點間的最短路徑(稱爲多源最短路徑問題)的一種算法,可以正確處理有向圖或負權的最短路徑問題。
算法思想
從任意節點i到任意節點j的最短路徑不外乎2種可能:
-
直接從節點i到節點j,
-
從節點i經過若干個節點k到節點j。
所以,我們假設 arcs(i,j) 爲節點 i 到節點 j 的最短路徑的距離,對於每一個節點 k,我們檢查 arcs(i,k) + arcs(k,j) < arcs(i,j) 是否成立,如果成立,證明從節點 i 到節點 k 再到節點 j 的路徑比節點 i 直接到節點 j 的路徑短,我們便設置 arcs(i,j) = arcs(i,k) + arcs(k,j),這樣一來,當我們遍歷完所有節點 k,arcs(i,j) 中記錄的便是節點i到節點j的最短路徑的距離。
其實上述這就是動態規劃了,直接上代碼吧,相比於採用貪心算法的狄傑斯特拉算法的 O(n2)的時間複雜度 --> 圖 —— 最短路徑(一)Dijkstra算法,弗洛伊德算法的時間複雜度爲 O(n3),效率比迪傑斯特拉算法低,但是它求的是多源最短路徑,實現起來也很簡單,沒啥說的,核心就5行代碼,下面來看代碼吧:
/**
* 弗洛伊德算法
* @param disIJ 傳入一個表示各個節點之間距離的數組
*/
public static void floyd(int[][] disIJ) {
for (int k = 0; k < disIJ.length; k++) {
for (int i = 0; i < disIJ.length; i++) {
for (int j = 0; j < disIJ[i].length; j++) {
if(disIJ[i][j] > disIJ[i][k]+disIJ[k][j]) {
disIJ[i][j] = disIJ[i][k]+disIJ[k][j];
}
}
}
}
}
嗯,沒錯,他就5行,我喜歡弗洛伊德!!!
下面來測試一下吧:
public static void main(String[] args) {
//自己到自己表示0 無法直接到達用65535表示
int[][] disIJ = {{0,50,65535,80,65535},
{65535,0,60,90,65535},
{65535,65535,0,65535,40},
{65535,65535,20,0,70},
{65535,50,65535,65535,0}};
floyd(disIJ);
for (int i = 0; i < disIJ.length; i++) {
for (int j = 0; j < disIJ[i].length; j++) {
System.out.print(disIJ[i][j]+"\t");
}
System.out.println();
}
}
不過,一般在面試題裏面它給的二維矩陣用0表示自己到自己,用 -1 表示無法到達(我使用的65535表示),那這個floyd方法就得稍微修改一下了:
/**
* 弗洛伊德算法
* @param disIJ 傳入一個表示各個節點之間距離的數組
*/
public static void floyd(int[][] disIJ) {
for (int k = 0; k < disIJ.length; k++) {
for (int i = 0; i < disIJ.length; i++) {
for (int j = 0; j < disIJ[i].length; j++) {
if(i == j) {
continue;
}
if(disIJ[i][k] != -1 && disIJ[k][j] != -1) {
if((disIJ[i][j] == -1 && disIJ[i][k]+disIJ[k][j] > -1) ||
(disIJ[i][j] > -1 && disIJ[i][j] > disIJ[i][k]+disIJ[k][j])) {
disIJ[i][j] = disIJ[i][k]+disIJ[k][j];
}
}
}
}
}
}
測試代碼:
public static void main(String[] args) {
int[][] disIJ = {{0,50,-1,80,-1},
{-1,0,60,90,-1},
{-1,-1,0,-1,40},
{-1,-1,20,0,70},
{-1,50,-1,-1,0}};
floyd(disIJ);
for (int i = 0; i < disIJ.length; i++) {
for (int j = 0; j < disIJ[i].length; j++) {
System.out.print(disIJ[i][j]+"\t");
}
System.out.println();
}
}
好了,現在就剩一個打印路徑的問題了,這個方法只是記錄裏最短路徑的值嘛,需要添加一個數組用來記錄最短路徑怎麼走:
/**
* 弗洛伊德算法
* @param disIJ 傳入一個表示各個節點之間距離的數組
*/
public static int[][] floyd(int[][] disIJ) {
int[][] path = new int[disIJ.length][disIJ[0].length];
//初始化path數組
for (int i = 0; i < disIJ.length; i++) {
for (int j = 0; j < disIJ[i].length; j++) {
path[i][j] = j;
}
}
for (int k = 0; k < disIJ.length; k++) {
for (int i = 0; i < disIJ.length; i++) {
for (int j = 0; j < disIJ.length; j++) {
if(disIJ[i][k] != -1 && disIJ[k][j] != -1) {
if((disIJ[i][j] == -1 && disIJ[i][k]+disIJ[k][j] > -1) ||
(disIJ[i][j] > -1 && disIJ[i][j] > disIJ[i][k]+disIJ[k][j])) {
disIJ[i][j] = disIJ[i][k]+disIJ[k][j];
path[i][j] = path[i][k];
}
}
}
}
}
return path;
}
這個返回的數組可以拿show方法查看一下:
public static void show(int[][] x) {
for (int i = 0; i < x.length; i++) {
for (int j = 0; j < x[i].length; j++) {
System.out.print(x[i][j]+"\t");
}
System.out.println();
}
System.out.println("========================");
}
測試代碼:
public static void main(String[] args) {
int[][] disIJ = {{0,50,-1,80,-1},
{-1,0,60,90,-1},
{-1,-1,0,-1,40},
{-1,-1,20,0,70},
{-1,50,-1,-1,0}};
int[][] path = floyd(disIJ);
show(disIJ);
show(path);
}
運行結果:
打印最短路徑,添加一個方法來打印:
public static void print(int[][] path) {
//i代表不同的起點
for (int i = 0; i < path.length; i++) {
//j代表不同的終點
for (int j = i+1; j < path.length; j++) { //j = i+1因爲不用算自己到自己
System.out.print(i); //起點
int k = path[i][j];
while (k != j) { //到達終點路徑就走完了
System.out.print("-->"+k);
k = path[k][j];
}
System.out.println("-->"+j);
}
System.out.println();
}
}