Java利用Dijkstra和Floyd分別求取圖的最短路徑

本文詳細介紹瞭圖的最短路徑的概念,然後介紹瞭求最短路徑的兩種算法:Dijkstra算法和Floyd算法的原理,最後提供瞭基於鄰接矩陣和鄰接表的圖對兩種算法的Java實現。

閱讀本文需要一定的圖的基礎,如果對於圖不是太明白的可以看看這篇文章:Java數據結構之圖的原理與實現。

1 最短路徑的概述

在生活中,圖形結構的應用是最廣泛的。比如常見的交通路線選擇,站點可以看作頂點,站點之間如果有路徑,則算作兩點之間的邊或者弧,站點之間的通行時間,可以看作邊或者弧的權值。

上圖就是生活中出行路線的選擇映射到圖形結構的案例。頂點作為站點,站點之間能夠到達則擁有邊,站點的之間的通行時間則是邊的權值。

對於出行路線的選擇,不同的人有不同的選擇。其中有一種很常見的選擇是要求出發地和目的地之間的總通行時間最短,而不在乎中途到底有幾站。畢竟通行時間對於很多人來說是寶貴的!

這樣的問題轉轉換為數學模型,就是求帶權圖的最短路徑,就是求帶權圖形兩頂點之間的權值最小的路徑。即如果從圖中某一頂點(源點)到達另一頂點(終點)的路徑可能不止一條,如何找到一條路徑使得沿此路徑上各邊的權值總和(稱為路徑長度)達到最小。

實際上最短路徑有兩重含義,一個兩頂點之間的路徑數量最少,另一個是兩頂點之間的路徑距離最短,本次主要解決路徑距離最短的問題,即最小權值和。常見的解決算法一般是兩種,迪傑斯特拉(Dijkstra)算法和弗洛伊德(Floyd)算法。

2 傑斯特拉(Dijkstra)算法

2.1 原理

迪傑斯特拉(Dijkstra)算法是由荷蘭計算機科學傢狄克斯特拉於1959 年提出的,因此又叫狄克斯特拉算法。是從一個頂點到其餘各頂點的最短路徑算法,解決的是尋找給定的加權圖中指定頂點間最短路徑問題的算法

Dijkstra算法並不是一下子就求出瞭起點到終點的最短路徑,而是采用的是貪心算法策略,一步步求出它們之間頂點的最短路徑,過程中都是基於已經求出的最短路徑的基礎上,求得更遠頂點的最短路徑,最終得到起點和終點的最短路徑。

通用步驟如下:

1.指定兩個集合S和U。S的作用是記錄已求出最短路徑的頂點,而U則是記錄還未求出最短路徑的頂點,以及這些頂點到起始頂點的權。

2.指定一個起始頂點A,存入集合S中,其他頂點以及到頂點A的權存入集合U中,從U中找出並移除路徑最短的頂點B,並將其加入到S中,並且更新U中對應的路徑權值(更新源點將新加入節點作為中間節點到達其它節點的距離);重復該操作直到遍歷所有頂點,此時S中的集合就是起點A到其他各個頂點的最短路徑。

迪傑斯特拉算法隻支持非負權圖,它計算的是單源最短路徑,即單個源點到剩餘節點的最短路徑,時間復雜度為O(n²),對稀疏圖運行更快。如果想知道所有頂點到所有頂點的最短路徑,那麼等於在原有算法的基礎上,再來一次循環,此時整個算法的時間復雜度就成瞭O(n³)。

2.2 案例分析

該案例對應著下面實現代碼中的案例,設起始點為A,初始化A到其他點的路徑數組{0, 99, 8, 2, 99, 3, 99},初始化標志位數組{true, false, false, false, false, false, false}。

開始第一輪循環,排除已找到的路徑,即排除0,尋找到A的最短路徑,找到瞭A-D,即索引為3的頂點,設置對應位置的最短路徑標志位為true,更新未獲取最短路徑的頂點的最短路徑,因為其他已找到的頂點的最短路徑已經找到瞭,這裡隻需要更新新找到的D可達的頂點路徑到A點的最短路徑,如果經過D點的路徑比數組中已存在的最短路徑小,那麼更新值。

這裡D點可達C、B,D-C+A-D=7<8,因此更新A-C的最短路徑為7;D-B+A-D=11<99,因此更新A-B的最短路徑為11,第一輪循環結束,此時最短路徑數組為{0, 11, 7, 2, 99, 3, 99},標志位數組為{true, false, false, true, false, false, false}:

開始第二輪循環,排除已找到的路徑,即排除0、2,尋找到A的最短路徑,這裡找到3,即索引為5的頂點,即A-F,設置對應位置的最短路徑標志位為true,更新未獲取最短路徑的頂點的最短路徑,因為其他已找到的頂點的最短路徑已經找到瞭,這裡隻需要更新新找到的F可達的頂點路徑到A點的最短路徑,如果經過F點的路徑比數組中已存在的最短路徑小,那麼更新值。

這裡F點可達G,F-G+A-F=12<99,因此更新A-G的最短路徑為12,第二輪循環結束,此時最短路徑數組為{0, 11, 7, 2, 99, 3, 12},標志位數組為{true, false, false, true, false, true, false}。

開始第三輪循環,排除已找到的路徑,即排除0、2、3,尋找到A的最短路徑,這裡找到7,即索引為2的頂點,即A-C,設置對應位置的最短路徑標志位為true,更新未獲取最短路徑的頂點的最短路徑,因為其他已找到的頂點的最短路徑已經找到瞭,這裡隻需要更新新找到的C可達的頂點路徑到A點的最短路徑,如果經過C點的路徑比數組中已存在的最短路徑小,那麼更新值。

這裡C點可達B,C-B+A-C=11 = 11,因此不更新A-B的最短路徑,第三輪循環結束,此時最短路徑數組為{0, 11, 7, 2, 99, 3, 12},標志位數組為{true, false, true, true, false, true, false}。

開始第四輪循環,排除已找到的路徑,即排除0、2、3、7,尋找到A的最短路徑,這裡找到11,即索引為1的頂點,即A-B,設置對應位置的最短路徑標志位為true,更新未獲取最短路徑的頂點的最短路徑,因為其他已找到的頂點的最短路徑已經找到瞭,這裡隻需要更新新找到的B可達的頂點路徑到A點的最短路徑,如果經過B點的路徑比數組中已存在的最短路徑小,那麼更新值。

這裡B點可達E,B-E+A-B=18 < 99,因此更新A-E的最短路徑為18,第四輪循環結束,此時最短路徑數組為{0, 11, 7, 2, 18, 3, 12},標志位數組為{true, true, true, true, false, true, false}。

開始第五輪循環,排除已找到的路徑,即排除0、2、3、7、11,尋找到A的最短路徑,這裡找到12,即索引為6的頂點,即A-G,設置對應位置的最短路徑標志位為true,更新未獲取最短路徑的頂點的最短路徑,因為其他已找到的頂點的最短路徑已經找到瞭,這裡隻需要更新新找到的G可達的頂點路徑到A點的最短路徑,如果經過G點的路徑比數組中已存在的最短路徑小,那麼更新值。

排除已找到的頂點,這裡G點可達E,G-E+A-G=18 = 18,因此不更新最短路徑,第五輪循環結束,此時最短路徑數組為{0, 11, 7, 2, 18, 3, 12},標志位數組為{true, true, true, true, false, true, true}。

開始第六輪循環,排除已找到的路徑,即排除0、2、3、7、11、12,尋找到A的最短路徑,這裡找到18,即索引為4的頂點,即A-E,設置對應位置的最短路徑標志位為true,更新未獲取最短路徑的頂點的最短路徑,因為其他已找到的頂點的最短路徑已經找到瞭,這裡隻需要更新新找到的E可達的頂點路徑到A點的最短路徑,如果經過E點的路徑比數組中已存在的最短路徑小,那麼更新值。

排除已找到的頂點,這裡不更新最短路徑,第四輪循環結束,此時最短路徑數組為{0, 11, 7, 2, 18, 3, 12},標志位數組為{true, true, true, true, true, true, true}。

此時大循環結束,Dijkstra算法結束,頂點A到各個頂點的最短路徑已經找到,即A到{A,B,C,D,E,F,G}的最短路徑為{0, 11, 7, 2, 18, 3, 12}。

3 弗洛伊德(Floyd)算法

3.1 原理

弗洛伊德(Floyd)算法又稱插點法,是一種利用動態規劃的思想尋找給定的加權圖中多源點之間最短路徑的算法。算出來的結果是所有的節點到其餘各節點之間的最短距離。

通用步驟如下:

1.設圖頂點數為N。首先需要準備一個長度為N的距離矩陣S,矩陣S中的元素a[i][j]=sum的表示頂點i到頂點j的最短路徑為sum;

2.然後對S矩陣進行初始化,距離矩陣S中頂點a[i][j]的值為頂點i到頂點j的直接權值;

3.然後對S矩陣循環進行N次更新,在第k次更新時,如果S矩陣的a[i][j] > a[i][k]+a[k][j],那麼更新a[i][j]=a[i][k]+a[k][j]。循環更新完畢,則算法完成,所有的節點到其餘各節點之間的最短距離已經找到瞭。

相比於Dijkstra 算法,Floyd算法支持帶有負權邊的圖,但是不能解決帶有“負權回路”(或者叫“負權環”)的圖,實際上如果一個圖中帶有“負權回路”那麼這個圖則沒有最短路徑。

Floyd算法的時間復雜度同樣是時間復雜度O(n³),空間復雜度是O(n²),代碼非常簡單,但是思想相卻是非常的巧妙,將所有的可能都枚舉出來一一對比,取最小值,這樣最終會得到最小值。

3.2 案例分析

該案例對應著下面實現代碼中的案例:

首先初始化距離矩陣S如下:

然後就是三層嵌套循環,開始第一輪大循環,即當k=0,循環遍歷S矩陣,判斷是否小於 shortestPath[i][j],即所有的路徑都經過A點中轉,如果經過A中轉後的路徑shortestPath[i][0] + shortestPath[k][0]< shortestPath[i][j],自然更新路徑:shortestPath[i][j]= shortestPath[i][0] + shortestPath[k][0]。一輪大循環之後的數組如下:

然後經過一共經過N次的大循環,表示經過所有的頂點,最終取得的矩陣如下:

4 鄰接矩陣加權圖實現

這裡的實現能夠構造一個基於鄰接矩陣實現無向加權圖的類,並且提供深度優先遍歷和廣度優先遍歷的方法,提供獲取邊集數組的方法,提供Prim和Kruskal兩種求最小生成樹的方法,提供Dijkstra和Floyd兩種求最短路徑的方法。

/**
 * 無向加權圖鄰接矩陣實現
 * {@link MatrixDijkstraAndFloyd#MatrixDijkstraAndFloyd(Object[], Edge[])}  構建無向加權圖
 * {@link MatrixDijkstraAndFloyd#DFS()}  深度優先遍歷無向加權圖
 * {@link MatrixDijkstraAndFloyd#BFS()}  廣度優先遍歷無向加權圖
 * {@link MatrixDijkstraAndFloyd#toString()}  輸出無向加權圖
 * {@link MatrixDijkstraAndFloyd#prim()}  Prim算法實現最小生成樹
 * {@link MatrixDijkstraAndFloyd#kruskal()}   Kruskal算法實現最小生成樹
 * {@link MatrixDijkstraAndFloyd#kruskalAndPrim()}  Kruskal算法結合Prim算法實現最小生成樹
 * {@link MatrixDijkstraAndFloyd#getEdges()}  獲取邊集數組
 * {@link MatrixDijkstraAndFloyd#dijkstra(int)} ()}  Dijkstra算法獲取指定頂點到所有頂點的最短路徑
 * {@link MatrixDijkstraAndFloyd#dijkstra(int, int)} Dijkstra算法獲取指定頂點到指定頂點的最短路徑
 * {@link MatrixDijkstraAndFloyd#floyd()} Floyd獲取所有頂點到所有頂點的最短路徑
 *
 * @author lx
 */
public class MatrixDijkstraAndFloyd<E> {

    /**
     * 頂點數組
     */
    private Object[] vertexs;

    /**
     * 鄰接矩陣
     */
    private int[][] matrix;

    /**
     *
     */
    private Edge<E>[] edges;

    /**
     * 由於是加權圖,這裡設置一個邊的權值上限,任何邊的最大權值不能大於等於該值,在實際應用中,該值應該根據實際情況確定
     */
    private static final int NO_EDGE = 99;


    /**
     * 邊對象,具有權值,在構建加權無向圖時使用
     */
    private static class Edge<E> {

        private E from;
        private E to;
        private int weight;

        public Edge(E from, E to, int weight) {
            this.from = from;
            this.to = to;
            this.weight = weight;
        }

        @Override
        public String toString() {
            return "Edge{" +
                    "from=" + from +
                    ", to=" + to +
                    ", weight=" + weight +
                    '}';
        }
    }

    /**
     * 創建無向加權圖
     *
     * @param vertexs 頂點數組
     * @param edges   邊對象數組
     */
    public MatrixDijkstraAndFloyd(Object[] vertexs, Edge<E>[] edges) {
        //初始化邊數組
        this.edges = edges;
        // 初始化頂點數組,並添加頂點
        this.vertexs = Arrays.copyOf(vertexs, vertexs.length);
        // 初始化邊矩陣,並預先填充邊信息
        this.matrix = new int[vertexs.length][vertexs.length];
        for (int i = 0; i < vertexs.length; i++) {
            for (int j = 0; j < vertexs.length; j++) {
                if (i == j) {
                    this.matrix[i][j] = 0;
                } else {
                    this.matrix[i][j] = NO_EDGE;
                }
            }
        }
        for (Edge<E> edge : edges) {
            // 讀取一條邊的起始頂點和結束頂點索引值
            int p1 = getPosition(edge.from);
            int p2 = getPosition(edge.to);
            //對稱的兩個點位都置為edge.weight,無向圖可以看作相互可達的有向圖
            this.matrix[p1][p2] = edge.weight;
            this.matrix[p2][p1] = edge.weight;
        }
    }

    /**
     * 獲取某條邊的某個頂點所在頂點數組的索引位置
     *
     * @param e 頂點的值
     * @return 所在頂點數組的索引位置, 或者-1 - 表示不存在
     */
    private int getPosition(E e) {
        for (int i = 0; i < vertexs.length; i++) {
            if (vertexs[i] == e) {
                return i;
            }
        }
        return -1;
    }


    /**
     * 深度優先搜索遍歷圖,類似於樹的前序遍歷,
     */
    public void DFS() {
        //新建頂點訪問標記數組,對應每個索引對應相同索引的頂點數組中的頂點
        boolean[] visited = new boolean[vertexs.length];
        //初始化所有頂點都沒有被訪問
        for (int i = 0; i < vertexs.length; i++) {
            visited[i] = false;
        }
        System.out.println("DFS: ");
        System.out.print("\t");
        for (int i = 0; i < vertexs.length; i++) {
            if (!visited[i]) {
                DFS(i, visited);
            }
        }
        System.out.println();
    }

    /**
     * 深度優先搜索遍歷圖的遞歸實現,類似於樹的先序遍歷
     * 因此模仿樹的先序遍歷,同樣借用棧結構,這裡使用的是方法的遞歸,隱式的借用棧
     *
     * @param i       頂點索引
     * @param visited 訪問標志數組
     */
    private void DFS(int i, boolean[] visited) {
        visited[i] = true;
        System.out.print(vertexs[i] + " ");
        // 遍歷該頂點的所有鄰接點。若該鄰接點是沒有訪問過,那麼繼續遞歸遍歷領接點
        for (int w = firstVertex(i); w >= 0; w = nextVertex(i, w)) {
            if (!visited[w]) {
                DFS(w, visited);
            }
        }
    }


    /**
     * 廣度優先搜索圖,類似於樹的層序遍歷
     * 因此模仿樹的層序遍歷,同樣借用隊列結構
     */
    public void BFS() {
        // 輔組隊列
        Queue<Integer> indexLinkedList = new LinkedList<>();
        //新建頂點訪問標記數組,對應每個索引對應相同索引的頂點數組中的頂點
        boolean[] visited = new boolean[vertexs.length];
        for (int i = 0; i < vertexs.length; i++) {
            visited[i] = false;
        }
        System.out.println("BFS: ");
        System.out.print("\t");
        for (int i = 0; i < vertexs.length; i++) {
            if (!visited[i]) {
                visited[i] = true;
                System.out.print(vertexs[i] + " ");
                indexLinkedList.add(i);
            }
            if (!indexLinkedList.isEmpty()) {
                //j索引出隊列
                Integer j = indexLinkedList.poll();
                //繼續訪問j的鄰接點
                for (int k = firstVertex(j); k >= 0; k = nextVertex(j, k)) {
                    if (!visited[k]) {
                        visited[k] = true;
                        System.out.print(vertexs[k] + " ");
                        //繼續入隊列
                        indexLinkedList.add(k);
                    }
                }
            }
        }
        System.out.println();
    }

    /**
     * 返回頂點v的第一個鄰接頂點的索引,失敗則返回-1
     *
     * @param v 頂點v在數組中的索引
     * @return 返回頂點v的第一個鄰接頂點的索引,失敗則返回-1
     */
    private int firstVertex(int v) {
        //如果索引超出范圍,則返回-1
        if (v < 0 || v > (vertexs.length - 1)) {
            return -1;
        }
        /*根據鄰接矩陣的規律:頂點索引v對應著邊二維矩陣的matrix[v][i]一行記錄
         * 從i=0開始*/
        for (int i = 0; i < vertexs.length; i++) {
            if (matrix[v][i] != 0 && matrix[v][i] != NO_EDGE) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 返回頂點v相對於w的下一個鄰接頂點的索引,失敗則返回-1
     *
     * @param v 頂點索引
     * @param w 第一個鄰接點索引
     * @return 返回頂點v相對於w的下一個鄰接頂點的索引,失敗則返回-1
     */
    private int nextVertex(int v, int w) {
        //如果索引超出范圍,則返回-1
        if (v < 0 || v > (vertexs.length - 1) || w < 0 || w > (vertexs.length - 1)) {
            return -1;
        }
        /*根據鄰接矩陣的規律:頂點索引v對應著邊二維矩陣的matrix[v][i]一行記錄
         * 由於鄰接點w的索引已經獲取瞭,所以從i=w+1開始尋找*/
        for (int i = w + 1; i < vertexs.length; i++) {
            if (matrix[v][i] != 0 && matrix[v][i] != NO_EDGE) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 輸出圖
     *
     * @return 輸出圖字符串
     */
    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < vertexs.length; i++) {
            for (int j = 0; j < vertexs.length; j++) {
                stringBuilder.append(matrix[i][j]).append("\t");
            }
            stringBuilder.append("\n");
        }
        return stringBuilder.toString();
    }

    /**
     * Prim算法求最小生成樹
     */
    public void prim() {
        System.out.println("prim: ");
        //對應節點應該被連接的前驅節點,用來輸出
        //默認為0,即前驅結點為第一個節點
        int[] mid = new int[matrix.length];
        //如果某頂點作為末端頂點被連接,對應位置應該為true
        //第一個頂點默認被連接
        boolean[] connected = new boolean[matrix.length];
        connected[0] = true;
        //存儲未連接頂點到已連接頂點的最短距離(最小權)
        int[] dis = new int[matrix.length];
        //首先將矩陣第一行即其他頂點到0索引頂點的權值拷貝進去
        System.arraycopy(matrix[0], 0, dis, 0, matrix.length);
        //存儲路徑長度
        int sum = 0;
        //最小權值
        int min;
        /*默認第一個頂點已經找到瞭,因此最多還要需要大循環n-1次*/
        for (int k = 1; k < matrix.length; k++) {
            min = NO_EDGE;
            //最小權值的頂點的索引
            int minIndex = 0;
            /*尋找權值最小的且未被連接的頂點索引*/
            for (int i = 1; i < matrix.length; i++) {
                //排除已連接的頂點,排除權值等於0的值,這裡權值等於0表示已生成的最小生成樹的頂點都未能與該頂點連接
                if (!connected[i] && dis[i] != 0 && dis[i] < min) {
                    min = dis[i];
                    minIndex = i;
                }
            }
            //如果沒找到,那麼該圖可能不是連通圖,直接返回瞭,此時最小生成樹沒啥意義
            if (minIndex == 0) {
                return;
            }
            //權值和增加
            sum += min;
            //該新連接頂點對應的索引值變成true,表示已被連接,後續判斷時跳過該頂點
            connected[minIndex] = true;
            //輸出對應的前驅頂點到該最小頂點的權值
            System.out.println("\t" + vertexs[mid[minIndex]] + " ---> " + vertexs[minIndex] + " 權值:" + min);
            /*在新頂點minIndex加入之前的其他所有頂點到連接頂點最小的權值已經計算過瞭
            因此隻需要更新其他未連接頂點到新連接頂點minIndex是否還有更短的權值,有的話就更新找到距離已連接的頂點權最小的頂點*/
            for (int i = 1; i < matrix.length; i++) {
                //如果該頂點未連接
                if (!connected[i]) {
                    /*如果新頂點到未連接頂點i的權值不為0,並且比原始頂點到未連接頂點i的權值還要小,那麼更新對應位置的最小權值*/
                    if (matrix[minIndex][i] != 0 && dis[i] > matrix[minIndex][i]) {
                        //更新最小權值
                        dis[i] = matrix[minIndex][i];
                        //更新前驅節點索引為新加入節點索引
                        mid[i] = minIndex;
                    }
                }

            }
        }
        System.out.println("\t" + "sum: " + sum);
    }


    /**
     * Kruskal算法求最小生成樹傳統實現,要求知道邊集數組,和頂點數組
     */
    public void kruskal() {
        System.out.println("Kruskal: ");
        //由於創建圖的時候保存瞭邊集數組,這裡直接使用就行瞭
        //Edge[] edges = getEdges();
        //this.edges=edges;
        //對邊集數組進行排序
        Arrays.sort(this.edges, Comparator.comparingInt(o -> o.weight));
        // 用於保存已有最小生成樹中每個頂點在該最小樹中的最終終點的索引
        int[] vends = new int[this.edges.length];
        //能夠知道終點索引范圍是[0,this.edges.length-1],因此填充edges.length表示沒有終點
        Arrays.fill(vends, this.edges.length);
        int sum = 0;
        for (Edge<E> edge : this.edges) {
            // 獲取第i條邊的起點索引from
            int from = getPosition(edge.from);
            // 獲取第i條邊的終點索引to
            int to = getPosition(edge.to);
            // 獲取頂點from在"已有的最小生成樹"中的終點
            int m = getEndIndex(vends, from);
            // 獲取頂點to在"已有的最小生成樹"中的終點
            int n = getEndIndex(vends, to);
            // 如果m!=n,意味著沒有形成環路,則可以添加,否則直接跳過,進行下一條邊的判斷
            if (m != n) {
                //添加設置原始終點索引m在已有的最小生成樹中的終點為n
                vends[m] = n;
                System.out.println("\t" + vertexs[from] + " ---> " + vertexs[to] + " 權值:" + edge.weight);
                sum += edge.weight;
            }
        }
        System.out.println("\t" + "sum: " + sum);
        //System.out.println(Arrays.toString(this.edges));
    }

    /**
     * 獲取頂點索引i的終點如果沒有終點則返回頂點索引本身
     *
     * @param vends 頂點在最小生成樹中的終點
     * @param i     頂點索引
     * @return 頂點索引i的終點如果沒有終點則返回頂點索引本身
     */
    private int getEndIndex(int[] vends, int i) {
        //這裡使用循環查找的邏輯,尋找的是最終的終點
        while (vends[i] != this.edges.length) {
            i = vends[i];
        }
        return i;
    }

    /**
     * 如果沒有現成的邊集數組,那麼根據鄰接矩陣結構獲取圖中的邊集數組
     *
     * @return 圖的邊集數組
     */
    private Edge[] getEdges() {
        List<Edge> edges = new ArrayList<>();
        /*遍歷矩陣數組 隻需要遍歷一半就行瞭*/
        for (int i = 0; i < vertexs.length; i++) {
            for (int j = i + 1; j < vertexs.length; j++) {
                //如果存在邊
                if (matrix[i][j] != NO_EDGE && matrix[i][j] != 0) {
                    edges.add(new Edge<>(vertexs[i], vertexs[j], matrix[i][j]));
                    //edges[index++] = new Edge(vertexs[i], vertexs[j], matrix[i][j]);
                }
            }
        }
        return edges.toArray(new Edge[0]);
    }

    /**
     * Kruskal結合Prim算法.不需要知道邊集,隻需要矩陣數組,和頂點數組
     * 同樣是求最小權值的邊,但是有一個默認起點頂點,該起點可以是要求[0,頂點數量-1]之間的任意值,同時查找最小權的邊。
     * 可能會有Bug,目前未發現
     */
    public void kruskalAndPrim() {
        System.out.println("kruskalAndPrim: ");
        //已經找到的邊攜帶的頂點對應的索引將變為true,其餘未找到邊對應的頂點將是false
        boolean[] connected = new boolean[matrix.length];
        //這裡選擇第一個頂點為起點,表示以該頂點開始尋找包含該頂點的最小邊
        connected[0] = true;
        int sum = 0, n1 = 0, n2 = 0;
        //最小權值
        int min;
        while (true) {
            min = NO_EDGE;
            /*找出所有帶有已找到頂點的邊中,最小權值的邊,隻需要尋找對稱矩陣的一半即可*/
            //第一維
            for (int i = 0; i < matrix.length; i++) {
                //第二維
                for (int j = i + 1; j < matrix.length; j++) {
                    //排除等於0的,排除兩個頂點都找到瞭的,這裡實際上已經隱含瞭排除環的邏輯,如果某條邊的兩個頂點都找到瞭,那麼如果算上該條邊,肯定會形成環
                    //尋找剩下的最小的權值的邊
                    if (matrix[i][j] != 0 && connected[i] != connected[j] && matrix[i][j] < min) {
                        min = matrix[i][j];
                        n1 = i;
                        n2 = j;
                    }
                }
            }
            //如果沒找到最小權值,該圖可能不是連通圖,或者已經尋找完畢,直接返回
            if (min == NO_EDGE) {
                System.out.println("\t" + "sum:" + sum);
                return;
            }
            //已經找到的邊對應的兩個頂點都置為true
            connected[n1] = true;
            connected[n2] = true;
            //輸出找到的邊和最小權值
            System.out.println("\t" + vertexs[n1] + " ---> " + vertexs[n2] + " 權值:" + min);
            sum += min;
        }
    }


    /**
     * Dijkstra算法求最短路徑。
     *
     * @param start 起始頂點索引。即計算"頂點vs"到其它頂點的最短路徑。
     */
    public void dijkstra(int start) {
        checkIndex(start);
        int[] shortestPathance = getShortestDistance(start, vertexs.length);
        // 打印Dijkstra最短路徑的結果
        System.out.println("Dijkstra(" + vertexs[start] + "):");
        for (int i = 0; i < vertexs.length; i++) {
            System.out.println("\t(" + vertexs[start] + " ---> " + vertexs[i] + ")最短路徑:" + shortestPathance[i]);
        }
    }

    /**
     * Dijkstra算法求最短路徑
     *
     * @param start 起始點
     * @param end   終點,如果end=vertexs.length說明是遍歷查找所有的最短路徑
     * @return 起始頂點到其他點或者指定點的最短權值
     */
    private int[] getShortestDistance(int start, int end) {
        /*1、該數組存放起始頂點到其他點的權值*/
        int[] shortestPathance = new int[vertexs.length];
        //初始化數據
        //首先設置起始點到頂點i到的最短路徑為起始點到頂點i的權。
        System.arraycopy(matrix[start], 0, shortestPathance, 0, matrix.length);

        /*2、標志位數組.某個位置如果為true表示對應位置的頂點到起始頂點的最短路徑已成功獲取。*/
        boolean[] shortest = new boolean[vertexs.length];
        //首先設置起始點到自己的的路徑已經找到瞭,為0
        shortest[start] = true;

        /*3、最多遍歷vertexs.length-1次;每次找出起始點到一個頂點的最短路徑。*/
        int k;
        int min;
        for (int i = 1; i < vertexs.length; i++) {
            k = 0;
            // 尋找當前最小的路徑;
            min = NO_EDGE;
            for (int j = 0; j < vertexs.length; j++) {
                //排除已經找到的最短路徑之後,找到離start最近的頂點(k)。
                if (!shortest[j] && shortestPathance[j] < min) {
                    min = shortestPathance[j];
                    k = j;
                }
            }
            //先設置起始點到新頂點k的最短路徑已經找到
            shortest[k] = true;
            if (end != vertexs.length && k == end) {
                break;
            }
            //更新未獲取最短路徑的頂點的最短路徑,因為其他已找到的頂點的最短路徑已經找到瞭,這裡指需要更新新加入的已找到的可達頂點的路徑.
            for (int j = 0; j < vertexs.length; j++) {
                int tmp = matrix[k][j];
                //排除已經找到的最短路徑,排除未連接的路徑,排除等於0的路徑(連接自己)之後
                //找到離start最如果新的最短路徑比以前的最短路徑還要短,則更新最短路徑。
                if (!shortest[j] && tmp != NO_EDGE && tmp != 0 && ((tmp = min + tmp) < shortestPathance[j])) {
                    shortestPathance[j] = tmp;
                }
            }
        }
        return shortestPathance;
    }

    /**
     * 索引檢查
     *
     * @param index 多個索引
     */
    private void checkIndex(int... index) {
        for (int i : index) {
            if (i < 0 || i >= vertexs.length) {
                throw new ArrayIndexOutOfBoundsException("索引越界:" + i);
            }
        }
    }

    /**
     * Dijkstra算法求最短路徑。
     *
     * @param start 起始頂點索引。
     * @param end   結束點索引
     */
    public void dijkstra(int start, int end) {
        checkIndex(start, end);
        int[] shortestPathance = getShortestDistance(start, end);
        // 打印Dijkstra最短路徑的結果
        System.out.println("Dijkstra(" + vertexs[start] + " ---> " + vertexs[end] + ")最短路徑:" + shortestPathance[end]);
    }


    /**
     * Floyd算法獲取所有頂點到所有頂點的最短路徑,代碼很簡單,思想很巧妙
     */
    public void floyd() {
        //路徑矩陣(兩頂點最短路徑,即最小權值)
        int[][] shortestPath = new int[matrix.length][matrix.length];
        /*初始化數據*/
        for (int i = 0; i < matrix.length; i++) {
            System.arraycopy(matrix[i], 0, shortestPath[i], 0, vertexs.length);
        }
        // 計算最短路徑
        for (int k = 0; k < matrix.length; k++) {
            for (int i = 0; i < matrix.length; i++) {
                for (int j = 0; j < matrix.length; j++) {
                    //要求經過下標k頂點的兩個路徑都不能等於NO_EDGE,否則就是沒有路徑,NO_EDGE應該選取的足夠的大,否則可能出錯
                    int tmp = (shortestPath[i][k] == NO_EDGE || shortestPath[k][j] == NO_EDGE) ? NO_EDGE : (shortestPath[i][k] + shortestPath[k][j]);
                    // 如果經過下標為k頂點路徑比原兩點間路徑更短,則更新shortestPath[i][j]
                    if (shortestPath[i][j] > tmp) {
                        // i到j最短路徑對應的值設為經過k的更小的一個
                        shortestPath[i][j] = tmp;
                    }

                }
            }
        }
        /*輸出路徑矩陣*/
        System.out.println("Floyd: ");
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix.length; j++) {
                System.out.print("\t" + shortestPath[i][j]);
            }
            System.out.println();
        }
    }


    public static void main(String[] args) {
        //頂點數組
        Character[] vexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        //邊數組,加權值
        Edge[] edges = {
                new Edge<>('A', 'C', 8),
                new Edge<>('D', 'A', 2),
                new Edge<>('A', 'F', 3),
                new Edge<>('B', 'C', 4),
                new Edge<>('C', 'D', 5),
                new Edge<>('E', 'G', 6),
                new Edge<>('E', 'B', 7),
                new Edge<>('D', 'B', 9),
                new Edge<>('F', 'G', 9)};

        //構建圖
        MatrixDijkstraAndFloyd<Character> matrixDijkstraAndFloyd = new MatrixDijkstraAndFloyd<Character>(vexs, edges);
        //輸出圖
        System.out.println(matrixDijkstraAndFloyd);
        //深度優先遍歷
        matrixDijkstraAndFloyd.DFS();
        //廣度優先遍歷
        matrixDijkstraAndFloyd.BFS();
        //Prim算法輸出最小生成樹
        matrixDijkstraAndFloyd.prim();
        //Kruskal算法輸出最小生成樹
        matrixDijkstraAndFloyd.kruskal();
        //Kruskal算法結合Prim算法輸出最小生成樹,可能會有Bug,目前未發現
        matrixDijkstraAndFloyd.kruskalAndPrim();


        // Dijkstra算法獲取某個索引的頂點到其它各個頂點的最短距離
        // 這裡參數是索引,也可以是一個頂點,需要稍微修改代碼獲取頂點的索引,比較簡單這裡就不做瞭
        matrixDijkstraAndFloyd.dijkstra(0);
        // Dijkstra算法獲取一個頂點到另一個頂點的最短距離
        matrixDijkstraAndFloyd.dijkstra(2, 0);

        // Floyd算法獲取所有頂點到所有頂點的最短路徑
        matrixDijkstraAndFloyd.floyd();
    }
}

5 鄰接表加權圖實現

這裡的實現能夠構造一個基於鄰接表實現無向加權圖的類;並且提供深度優先遍歷和廣度優先遍歷的方法,提供獲取邊集數組的方法,提供Prim和Kruskal兩種求最小生成樹的方法,提供Dijkstra和Floyd兩種求最短路徑的方法。

/**
 * 無向加權圖鄰接表實現
 * {@link ListDijkstraAndFloyd#ListDijkstraAndFloyd(Object[], Edge[])} 構建無向加權圖
 * {@link ListDijkstraAndFloyd#DFS()}  深度優先遍歷無向加權圖
 * {@link ListDijkstraAndFloyd#BFS()}  廣度優先遍歷無向加權圖
 * {@link ListDijkstraAndFloyd#toString()}  輸出無向加權圖
 * {@link ListDijkstraAndFloyd#prim()}  Prim算法實現最小生成樹
 * {@link ListDijkstraAndFloyd#kruskal()}   Kruskal算法實現最小生成樹
 * {@link ListDijkstraAndFloyd#getEdges()}  獲取邊集數組
 * {@link ListDijkstraAndFloyd#dijkstra(int)} ()}  獲取指定頂點到所有頂點的最短路徑
 * {@link ListDijkstraAndFloyd#dijkstra(int, int)} 獲取指定頂點到指定頂點的最短路徑
 * {@link ListDijkstraAndFloyd#floyd()} Floyd獲取所有頂點到所有頂點的最短路徑
 *
 * @author lx
 */
public class ListDijkstraAndFloyd<E> {
    /**
     * 頂點類
     *
     * @param <E>
     */
    private class Node<E> {
        /**
         * 頂點信息
         */
        E data;
        /**
         * 指向第一條依附該頂點的邊
         */
        LNode firstLNode;

        public Node(E data, LNode firstLNode) {
            this.data = data;
            this.firstLNode = firstLNode;
        }
    }

    /**
     * 邊表節點類
     */
    private class LNode {
        /**
         * 該邊所指向的頂點的索引位置
         */
        int vertex;
        /**
         * 該邊的權值
         */
        int weight;
        /**
         * 指向下一條邊的指針
         */
        LNode nextLNode;
    }

    /**
     * 邊對象,具有權值,在構建加權無向圖時使用
     */
    private static class Edge<E> {

        private E from;
        private E to;
        private int weight;

        public Edge(E from, E to, int weight) {
            this.from = from;
            this.to = to;
            this.weight = weight;
        }

        @Override
        public String toString() {
            return "Edge{" +
                    "from=" + from +
                    ", to=" + to +
                    ", weight=" + weight +
                    '}';
        }
    }

    /**
     * 頂點數組
     */
    private Node<E>[] vertexs;


    /**
     * 邊數組
     */
    private Edge<E>[] edges;

    /**
     * 由於是加權圖,這裡設置一個邊的權值上限,任何邊的最大權值不能大於等於該值,在實際應用中,該值應該根據實際情況確定
     */
    private static final int NO_EDGE = 99;

    /**
     * 創建無向加權圖
     *
     * @param vexs  頂點數組
     * @param edges 邊二維數組
     */
    public ListDijkstraAndFloyd(E[] vexs, Edge<E>[] edges) {
        this.edges = edges;
        /*初始化頂點數組,並添加頂點*/
        vertexs = new Node[vexs.length];
        for (int i = 0; i < vertexs.length; i++) {
            vertexs[i] = new Node<>(vexs[i], null);
        }
        /*初始化邊表,並添加邊節點到邊表尾部,即采用尾插法*/
        for (Edge<E> edge : edges) {
            // 讀取一條邊的起始頂點和結束頂點索引值
            int p1 = getPosition(edge.from);
            int p2 = getPosition(edge.to);
            int weight = edge.weight;
            /*這裡需要相互添加邊節點,無向圖可以看作相互可達的有向圖*/
            // 初始化lnode1邊節點
            LNode lnode1 = new LNode();
            lnode1.vertex = p2;
            lnode1.weight = weight;
            // 將LNode鏈接到"p1所在鏈表的末尾"
            if (vertexs[p1].firstLNode == null) {
                vertexs[p1].firstLNode = lnode1;
            } else {
                linkLast(vertexs[p1].firstLNode, lnode1);
            }
            // 初始化lnode2邊節點
            LNode lnode2 = new LNode();
            lnode2.vertex = p1;
            lnode2.weight = weight;
            // 將lnode2鏈接到"p2所在鏈表的末尾"
            if (vertexs[p2].firstLNode == null) {
                vertexs[p2].firstLNode = lnode2;
            } else {
                linkLast(vertexs[p2].firstLNode, lnode2);
            }
        }
    }

    /**
     * 獲取某條邊的某個頂點所在頂點數組的索引位置
     *
     * @param e 頂點的值
     * @return 所在頂點數組的索引位置, 或者-1 - 表示不存在
     */
    private int getPosition(E e) {
        for (int i = 0; i < vertexs.length; i++) {
            if (vertexs[i].data == e) {
                return i;
            }
        }
        return -1;
    }


    /**
     * 將lnode節點鏈接到邊表的最後,采用尾插法
     *
     * @param first 邊表頭結點
     * @param node  將要添加的節點
     */
    private void linkLast(LNode first, LNode node) {
        while (true) {
            if (first.vertex == node.vertex) {
                return;
            }
            if (first.nextLNode == null) {
                break;
            }
            first = first.nextLNode;
        }
        first.nextLNode = node;
    }

    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < vertexs.length; i++) {
            stringBuilder.append(i).append("(").append(vertexs[i].data).append("): ");
            LNode node = vertexs[i].firstLNode;
            while (node != null) {
                stringBuilder.append(node.vertex).append("(").append(vertexs[node.vertex].data).append("-").append(node.weight).append(")");
                node = node.nextLNode;
                if (node != null) {
                    stringBuilder.append("->");
                } else {
                    break;
                }
            }
            stringBuilder.append("\n");
        }
        return stringBuilder.toString();
    }

    /**
     * 深度優先搜索遍歷圖的遞歸實現,類似於樹的先序遍歷
     * 因此模仿樹的先序遍歷,同樣借用棧結構,這裡使用的是方法的遞歸,隱式的借用棧
     *
     * @param i       頂點索引
     * @param visited 訪問標志數組
     */
    private void DFS(int i, boolean[] visited) {
        //索引索引標記為true ,表示已經訪問瞭
        visited[i] = true;
        System.out.print(vertexs[i].data + " ");
        //獲取該頂點的邊表頭結點
        LNode node = vertexs[i].firstLNode;
        //循環遍歷該頂點的鄰接點,采用同樣的方式遞歸搜索
        while (node != null) {
            if (!visited[node.vertex]) {
                DFS(node.vertex, visited);
            }
            node = node.nextLNode;
        }
    }

    /**
     * 深度優先搜索遍歷圖,類似於樹的前序遍歷,
     */
    public void DFS() {
        //新建頂點訪問標記數組,對應每個索引對應相同索引的頂點數組中的頂點
        boolean[] visited = new boolean[vertexs.length];
        //初始化所有頂點都沒有被訪問
        for (int i = 0; i < vertexs.length; i++) {
            visited[i] = false;
        }
        System.out.println("DFS: ");
        System.out.print("\t");
        /*循環搜索*/
        for (int i = 0; i < vertexs.length; i++) {
            //如果對應索引的頂點的訪問標記為false,則搜索該頂點
            if (!visited[i]) {
                DFS(i, visited);
            }
        }
        /*走到這一步,說明頂點訪問標記數組全部為true,說明全部都訪問到瞭,深度搜索結束*/
        System.out.println();
    }

    /**
     * 廣度優先搜索圖,類似於樹的層序遍歷
     * 因此模仿樹的層序遍歷,同樣借用隊列結構
     */
    public void BFS() {
        // 輔組隊列
        Queue<Integer> indexLinkedList = new LinkedList<>();
        //新建頂點訪問標記數組,對應每個索引對應相同索引的頂點數組中的頂點
        boolean[] visited = new boolean[vertexs.length];
        //初始化所有頂點都沒有被訪問
        for (int i = 0; i < vertexs.length; i++) {
            visited[i] = false;
        }
        System.out.println("BFS: ");
        System.out.print("\t");
        for (int i = 0; i < vertexs.length; i++) {
            //如果訪問方劑為false,則設置為true,表示已經訪問,然後開始訪問
            if (!visited[i]) {
                visited[i] = true;
                System.out.print(vertexs[i].data + " ");
                indexLinkedList.add(i);
            }
            //判斷隊列是否有值,有就開始遍歷
            if (!indexLinkedList.isEmpty()) {
                //出隊列
                Integer j = indexLinkedList.poll();
                LNode node = vertexs[j].firstLNode;
                while (node != null) {
                    int k = node.vertex;
                    if (!visited[k]) {
                        visited[k] = true;
                        System.out.print(vertexs[k].data + " ");
                        //繼續入隊列
                        indexLinkedList.add(k);
                    }
                    node = node.nextLNode;
                }
            }
        }
        System.out.println();
    }

    /**
     * Prim算法求最小生成樹
     */
    public void prim() {
        System.out.println("prim: ");
        //對應節點應該被連接的前驅節點,用來輸出
        //默認為0,即前驅結點為第一個節點
        int[] mid = new int[vertexs.length];
        int start = 0;
        int min, tmp, sum = 0;
        int num = vertexs.length;

        //頂點間邊的權值
        //存儲未連接頂點到已連接頂點的最短距離(最小權)
        int[] dis = new int[num];


        // 初始化"頂點的權值數組",
        // 將每個頂點的權值初始化為"第start個頂點"到"該頂點"的權值。
        //首先將其他頂點到0索引頂點的權值存儲進去
        for (int i = 0; i < num; i++) {
            dis[i] = getWeight(start, i);
        }
        //如果某頂點作為末端頂點被連接,對應位置應該為true
        //第一個頂點默認被連接
        boolean[] connected = new boolean[vertexs.length];
        connected[0] = true;
        /*默認第一個頂點已經找到瞭,因此最多還要需要大循環n-1次*/
        for (int k = 1; k < num; k++) {
            min = NO_EDGE;
            //最小權值的頂點的索引
            int minIndex = 0;
            // 在未被加入到最小生成樹的頂點中,找出權值最小的頂點。
            for (int i = 1; i < vertexs.length; i++) {
                //排除已連接的頂點,排除權值等於0的值,因為這裡默認頂點指向自己的權值為0
                if (!connected[i] && dis[i] != 0 && dis[i] < min) {
                    min = dis[i];
                    minIndex = i;
                }
            }
            //如果沒找到,那麼該圖可能不是連通圖,直接返回瞭,此時最小生成樹沒啥意義
            if (minIndex == 0) {
                return;
            }
            //權值和增加
            sum += min;
            //該新連接頂點對應的索引值變成true,表示已被連接,後續判斷時跳過該頂點
            connected[minIndex] = true;
            //輸出對應的前驅頂點到該最小頂點的權值
            System.out.println("\t" + vertexs[mid[minIndex]].data + " ---> " + vertexs[minIndex].data + " 權值:" + min);
            /*在新頂點minIndex加入之前的其他所有頂點到連接頂點最小的權值已經計算過瞭
            因此隻需要更新其他頂點到新連接頂點minIndex是否還有更短的權值,有的話就更新找到距離已連接的頂點權最小的頂點*/
            for (int i = 1; i < num; i++) {
                //如果該頂點未連接
                if (!connected[i]) {
                    // 獲取minindex頂點到未連接頂點i的權值
                    tmp = getWeight(minIndex, i);
                    /*如果新頂點到未連接頂點i的權值不為0,並且比原始頂點到未連接頂點i的權值還要小,那麼更新對應位置的最小權值*/
                    if (tmp != 0 && dis[i] > tmp) {
                        dis[i] = tmp;
                        //更新前驅節點索引為新加入節點索引
                        mid[i] = minIndex;
                    }
                }
            }
        }
        System.out.println("\t" + "sum: " + sum);
    }

    /**
     * 嘗試獲取邊起點start到邊終點end的邊的權值,當然可能獲取不到
     *
     * @param start 邊起點
     * @param end   邊終點
     * @return 返回權值; 如果起點和終點相同則返回0;如果邊起點和邊終點之間並沒有邊, 則返回NO_EDGE
     */
    private int getWeight(int start, int end) {
        //如果start=end,則返回0
        if (start == end) {
            return 0;
        }
        //獲取該頂點的邊表的第一個值
        LNode node = vertexs[start].firstLNode;
        //循環查找邊表,看能否找到對應的索引=end,找不到就返回NO_EDGE,表示兩個頂點未連接。
        while (node != null) {
            if (end == node.vertex) {
                return node.weight;
            }
            node = node.nextLNode;
        }
        return NO_EDGE;
    }

    /**
     * Kruskal算法求最小生成樹,可以說鄰接矩陣和鄰接鏈表的實現方式是完全一致的
     */
    public void kruskal() {
        System.out.println("Kruskal: ");
        //由於創建圖的時候保存瞭邊集數組,這裡直接使用就行瞭
        //Edge[] edges = getEdges();
        //this.edges=edges;
        //對邊集數組進行排序
        Arrays.sort(this.edges, Comparator.comparingInt(o -> o.weight));
        // 用於保存已有最小生成樹中每個頂點在該最小樹中的最終終點的索引
        int[] vends = new int[this.edges.length];
        //能夠知道終點索引范圍是[0,this.edges.length-1],因此填充edges.length表示沒有終點
        Arrays.fill(vends, this.edges.length);
        int sum = 0;
        for (Edge<E> edge : this.edges) {
            // 獲取第i條邊的起點索引from
            int from = getPosition(edge.from);
            // 獲取第i條邊的終點索引to
            int to = getPosition(edge.to);
            // 獲取頂點from在"已有的最小生成樹"中的終點
            int m = getEndIndex(vends, from);
            // 獲取頂點to在"已有的最小生成樹"中的終點
            int n = getEndIndex(vends, to);
            // 如果m!=n,意味著沒有形成環路,則可以添加,否則直接跳過,進行下一條邊的判斷
            if (m != n) {
                //添加設置原始終點索引m在已有的最小生成樹中的終點為n
                vends[m] = n;
                System.out.println("\t" + vertexs[from].data + " ---> " + vertexs[to].data + " 權值:" + edge.weight);
                sum += edge.weight;
            }
        }
        System.out.println("\t" + "sum: " + sum);
        //System.out.println(Arrays.toString(this.edges));
    }

    /**
     * 獲取頂點索引i的終點如果沒有終點則返回頂點索引本身
     *
     * @param vends 頂點在最小生成樹中的終點
     * @param i     頂點索引
     * @return 頂點索引i的終點如果沒有終點則返回頂點索引本身
     */
    private int getEndIndex(int[] vends, int i) {
        //這裡使用循環查找的邏輯,尋找的是最終的終點
        while (vends[i] != this.edges.length) {
            i = vends[i];
        }
        return i;
    }

    /**
     * 如果沒有現成的邊集數組,那麼根據鄰接表結構獲取圖中的邊集數組
     *
     * @return 圖的邊集數組
     */
    private Edge[] getEdges() {
        List<Edge> edges = new ArrayList<>();
        //遍歷頂點數組
        for (int i = 0; i < vertexs.length; i++) {
            LNode node = vertexs[i].firstLNode;
            while (node != null) {
                //隻需添加起點索引小於終點索引的邊就行瞭
                if (node.vertex > i) {
                    edges.add(new Edge<>(vertexs[i].data, vertexs[node.vertex].data, node.weight));
                }
                node = node.nextLNode;
            }
        }
        return edges.toArray(new Edge[0]);
    }

    /**
     * Dijkstra算法求最短路徑。
     *
     * @param start 起始頂點索引。即計算"頂點vs"到其它頂點的最短路徑。
     */
    public void dijkstra(int start) {
        checkIndex(start);
        int[] distance = getShortestDistance(start, vertexs.length);
        // 打印dijkstra最短路徑的結果
        System.out.println("dijkstra(" + vertexs[start].data + "):");
        for (int i = 0; i < vertexs.length; i++) {
            System.out.println("\t(" + vertexs[start].data + " ---> " + vertexs[i].data + ")最短路徑:" + distance[i]);
        }
    }

    /**
     * Dijkstra算法求最短路徑。
     *
     * @param start 起始頂點索引。
     * @param end   結束點索引
     */
    public void dijkstra(int start, int end) {
        checkIndex(start, end);
        int[] shortestPathance = getShortestDistance(start, end);
        // 打印dijkstra最短路徑的結果
        System.out.println("Dijkstra(" + vertexs[start].data + " ---> " + vertexs[end].data + ")最短路徑:" + shortestPathance[end]);
    }

    /**
     * Dijkstra算法求最短路徑
     *
     * @param start 起始點
     * @param end   終點,如果end=vertexs.length說明是遍歷查找所有的最短路徑
     * @return 起始頂點到其他點或者指定點的最短權值
     */
    private int[] getShortestDistance(int start, int end) {
        /*1、該數組存放起始頂點到其他點的權值*/
        int[] distance = new int[vertexs.length];
        //初始化數據
        for (int i = 0; i < vertexs.length; i++) {
            //首先設置起始點到頂點i到的最短路徑為起始點到頂點i的權。
            distance[i] = getWeight(start, i);
        }

        /*2、標志位數組.某個位置表示true表示,對應位置的頂點到起始頂點的最短路徑已成功獲取。*/
        boolean[] shortest = new boolean[vertexs.length];
        //首先設置起始點到自己的的路徑已經找到瞭,為0
        shortest[start] = true;


        /*3、最多遍歷vertexs.length-1次;每次找出起始點到一個頂點的最短路徑。*/
        int k;
        int min;
        for (int i = 1; i < vertexs.length; i++) {
            k = 0;
            // 尋找當前最小的路徑;
            min = NO_EDGE;
            for (int j = 0; j < vertexs.length; j++) {
                //排除已經找到的最短路徑之後,找到離start最近的頂點(k)。
                if (!shortest[j] && distance[j] < min) {
                    min = distance[j];
                    k = j;
                }
            }
            //先設置起始點到新頂點k的最短路徑已經找到
            shortest[k] = true;
            if (end != vertexs.length && k == end) {
                break;
            }
            //更新未獲取最短路徑的頂點的最短路徑,因為其他已找到的頂點的最短路徑已經找到瞭,這裡指需要更新新加入的已找到的可達頂點的路徑.
            for (int j = 0; j < vertexs.length; j++) {
                int tmp = getWeight(k, j);
                //排除已經找到的最短路徑,排除未連接的路徑,排除等於0的路徑(連接自己)之後
                //找到離start最如果新的最短路徑比以前的最短路徑還要短,則更新最短路徑。
                if (!shortest[j] && tmp != NO_EDGE && tmp != 0 && ((tmp = min + tmp) < distance[j])) {
                    distance[j] = tmp;
                }
            }
        }
        return distance;
    }


    /**
     * 索引檢查
     *
     * @param index 多個索引
     */
    private void checkIndex(int... index) {
        for (int i : index) {
            if (i < 0 || i >= vertexs.length) {
                throw new ArrayIndexOutOfBoundsException("索引越界:" + i);
            }
        }
    }

    /**
     * Floyd算法獲取所有頂點到所有頂點的最短路徑,與鄰接矩陣的實現基本一致
     */
    public void floyd() {
        //路徑矩陣(兩頂點最短路徑,即最小權值)
        int[][] shortestPath = new int[vertexs.length][vertexs.length];
        /*初始化數據*/
        for (int i = 0; i < vertexs.length; i++) {
            for (int j = 0; j < vertexs.length; j++) {
                //獲取兩點的直接權值
                //如果是頻繁調用該方法,因此可以創建一個屬於對象的權值矩陣用來保存權值,這裡為瞭簡單沒做
                shortestPath[i][j] = getWeight(i, j);
            }
        }
        // 計算最短路徑
        for (int k = 0; k < vertexs.length; k++) {
            for (int i = 0; i < vertexs.length; i++) {
                for (int j = 0; j < vertexs.length; j++) {
                    //要求經過下標k頂點的兩個路徑都不能等於NO_EDGE,否則就是沒有路徑,NO_EDGE應該選取的足夠的大,否則可能出錯
                    int tmp = (shortestPath[i][k] == NO_EDGE || shortestPath[k][j] == NO_EDGE) ? NO_EDGE : (shortestPath[i][k] + shortestPath[k][j]);
                    // 如果經過下標為k頂點路徑比原兩點間路徑更短,則更新shortestPath[i][j]
                    if (shortestPath[i][j] > tmp) {
                        // i到j最短路徑對應的值設為經過k的更小的一個
                        shortestPath[i][j] = tmp;
                    }

                }
            }
        }
        /*輸出路徑矩陣*/
        System.out.println("Floyd: ");
        for (int i = 0; i < vertexs.length; i++) {
            for (int j = 0; j < vertexs.length; j++) {
                System.out.print("\t" + shortestPath[i][j]);
            }
            System.out.println();
        }
    }


    public static void main(String[] args) {
        //頂點數組
        Character[] vexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        //邊數組,加權值
        Edge[] edges = {
                new Edge<>('A', 'C', 8),
                new Edge<>('D', 'A', 2),
                new Edge<>('A', 'F', 3),
                new Edge<>('B', 'C', 4),
                new Edge<>('C', 'D', 5),
                new Edge<>('E', 'G', 6),
                new Edge<>('E', 'B', 7),
                new Edge<>('D', 'B', 9),
                new Edge<>('F', 'G', 9)};
        //構建圖
        ListDijkstraAndFloyd<Character> listDijkstraAndFloyd = new ListDijkstraAndFloyd<Character>(vexs, edges);
        //輸出圖
        System.out.println(listDijkstraAndFloyd);
        //深度優先遍歷
        //DFS:
        //A C B E G F D
        listDijkstraAndFloyd.DFS();
        //廣度優先遍歷
        //BFS:
        //A C D F B G E
        listDijkstraAndFloyd.BFS();
        //Prim算法求最小生成樹
        listDijkstraAndFloyd.prim();
        //Kruskal算法求最小生成樹
        listDijkstraAndFloyd.kruskal();


        // Dijkstra算法獲取某個索引的頂點到其它各個頂點的最短距離
        // 這裡參數是索引,也可以是一個頂點,需要稍微修改代碼獲取頂點的索引,比較簡單這裡就不做瞭
        listDijkstraAndFloyd.dijkstra(0);
        // Dijkstra算法獲取一個頂點到另一個頂點的最短距離
        listDijkstraAndFloyd.dijkstra(2, 0);

        // Floyd算法獲取所有頂點到所有頂點的最短路徑
        listDijkstraAndFloyd.floyd();
    }

}

以上就是Java利用Dijkstra和Floyd分別求取圖的最短路徑的詳細內容,更多關於Java求最短路徑的資料請關註WalkonNet其它相關文章!

推薦閱讀: