圖 —— 最短路徑(二)Floyd算法

Floyd最短路算法(全局/多源最短路)

求任意兩個點之間的最短路徑。這個問題這也被稱爲“多源最短路徑”問題。

Floyd算法是一個經典的動態規劃算法。是解決任意兩點間的最短路徑(稱爲多源最短路徑問題)的一種算法,可以正確處理有向圖或負權的最短路徑問題。

算法思想

從任意節點i到任意節點j的最短路徑不外乎2種可能:

  1. 直接從節點i到節點j,

  2. 從節點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();
        }
    }

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章