圖解最短路徑之迪傑斯特拉算法(Java實現)

概述

迪傑斯特拉算法是由荷蘭計算機科學家狄克斯特拉於1959 年提出的,因此又叫狄克斯特拉算法。是從一個頂點到其餘各頂點的最短路徑算法,解決的是有權圖中最短路徑問題。迪傑斯特拉算法主要特點是以起始點爲中心向外層層擴展,直到擴展到終點爲止。迪傑斯特拉算法採用的是貪心策略,將Graph中的節點集分爲最短路徑計算完成的節點集S和未計算完成的節點集T,每次將從T中挑選V0->Vt最小的節點Vt加入S,並更新V0經由Vt到T中剩餘節點的更短距離,直到T中的節點全部加入S中,它貪心就貪心在每次都選擇一個距離源點最近的節點加入最短路徑節點集合。迪傑斯特拉算法只支持非負權圖,它計算的是單源最短路徑,即單個源點到剩餘節點的最短路徑,時間複雜度爲O(n²)。

算法描述

在有向圖 G=(V,E) 中,假設每條邊 E[i] 的長度爲 w[i],找到由頂點 V0 到其餘各點的最短值。

算法流程

本節將對算法流程進行模擬,設置Graph爲包含7個頂點和9條邊的有向無環圖,源點爲0,計算從源點0到剩餘節點的最短路徑,Graph如下:

每個節點將維護shortest和visited兩個數據結構,shortest存儲v0到該節點的最短路徑,visited存儲v0到該節點的最短路徑是否求出。S爲已求出最短路徑的節點,T爲未求出最短路徑的節點。源節點只允許將S中的節點作爲中間節點來計算到達其它節點的最短路徑,不允許將T中的節點作爲中間節點來計算到達其它節點的最短路徑。隨着S中節點的增加,源節點可達的節點纔會增加。初始狀態下,源節點只可達節點1和節點3。

算法步驟如下:

1、將源節點(即節點0)加入S中,對shortest和visited數組進行更新。

2、S中現有節點0,源節點可達T中的節點1和節點3,節點0->節點1距離爲6,節點0->節點3距離爲2,按距離從小到大排序,因此選擇將節點3加入S中。更新源點將節點3作爲中間節點到達其它節點的距離。

3、S中現有節點0和節點3,源節點可達T中的節點1和4,節點0->節點1距離爲6,節點0->節點4距離爲7,按距離從小到大排序,因此選擇將節點1加入S中。更新源點將節點1作爲中間節點到達其它節點的距離。

4、S中現有節點0、1、3,源節點可達T中的節點2、4、5,0->2距離爲11,0->4距離爲7,0->5距離爲9,按距離從小到大排序,因此選擇將節點4加入S中。更新源點將節點4作爲中間節點到達其它節點的距離。

5、S中現有節點0、1、3、4,源節點可達T中的節點2、5、6,0->2距離爲11,0->5距離爲9,0->6距離爲8,按距離從小到大排序,因此選擇將節點6加入S中。更新源點將節點6作爲中間節點到達其它節點的距離。

6、S中現有節點0、1、3、4、6,源節點可達T中的節點2、5,0->2距離爲11,0->5距離爲9,按距離從小到大排序,因此選擇將節點5加入S中。更新源點將節點5作爲中間節點到達其它節點的距離。

7、T中只剩下節點2,0->2距離爲11,將節點2加入S中。

8、算法結束,源點到其它節點的最短路徑都已依次求出。

算法實現

public class DijstraAlgorithm {
    //不能設置爲Integer.MAX_VALUE,否則兩個Integer.MAX_VALUE相加會溢出導致出現負權
    public static int MaxValue = 100000;
    
    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;
            }
        }
        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;
        }

        //單源最短路徑,源點
        int source = input.nextInt();
        //調用dijstra算法計算最短路徑
        dijstra(matrix, source);
    }

    public static void dijstra(int[][] matrix, int source) {
        //最短路徑長度
        int[] shortest = new int[matrix.length];
        //判斷該點的最短路徑是否求出
        int[] visited = new int[matrix.length];
        //存儲輸出路徑
        String[] path = new String[matrix.length];

        //初始化輸出路徑
        for (int i = 0; i < matrix.length; i++) {
            path[i] = new String(source + "->" + i);
        }

        //初始化源節點
        shortest[source] = 0;
        visited[source] = 1;

        for (int i = 1; i < matrix.length; i++) {
            int min = Integer.MAX_VALUE;
            int index = -1;

            for (int j = 0; j < matrix.length; j++) {
                //已經求出最短路徑的節點不需要再加入計算並判斷加入節點後是否存在更短路徑
                if (visited[j] == 0 && matrix[source][j] < min) {
                    min = matrix[source][j];
                    index = j;
                }
            }

            //更新最短路徑
            shortest[index] = min;
            visited[index] = 1;

            //更新從index跳到其它節點的較短路徑
            for (int m = 0; m < matrix.length; m++) {
                if (visited[m] == 0 && matrix[source][index] + matrix[index][m] < matrix[source][m]) {
                    matrix[source][m] = matrix[source][index] + matrix[index][m];
                    path[m] = path[index] + "->" + m;
                }
            }

        }

        //打印最短路徑
        for (int i = 0; i < matrix.length; i++) {
            if (i != source) {
                if (shortest[i] == MaxValue) {
                    System.out.println(source + "到" + i + "不可達");
                } else {
                    System.out.println(source + "到" + i + "的最短路徑爲:" + path[i] + ",最短距離是:" + shortest[i]);
                }
            }
        }
    }
}

樣例輸入:

7 10

0 1 6

1 2 5

0 3 2

3 1 7

3 4 5

1 2 5

1 5 3

4 5 5

5 4 2

4 6 1

0

Q&A

問:爲什麼迪傑斯特拉算法只支持非負權的圖?

答:迪傑斯特拉採用的貪心策略,S集合中是已經計算出最短路徑的節點,T集合中是未計算出最短路徑的節點。假設存在負權,源點爲A,已經計算出A->B的最短路徑爲m,若下一次將C添加進已計算出最短路徑的節點集合,而A->C=m,C->B=-1,則會出現A->B的更短路徑A->C->B,但迪傑斯特拉不會對已經計算出最短路徑的節點重新計算,因此無法更新最短路徑,即負權的出現導致無法保證S中節點計算的是最短路徑,已經固定dis的點可能會被其它dis大於它的點更新。

問:爲什麼在代碼實現中不能將節點之間不可達用Integer.MAX_VALUE代表?

答:因爲兩個Integer.MAX_VALUE相加會溢出導致出現負權,所以最好設置爲一個比較大且不容易相加溢出的數。

問:迪傑斯特拉算法適用於什麼場景?

答:在有些算法書上說,迪傑斯特拉適用於DAG(有向無環圖)。但是個人覺得,它所謂的“適用於”,或許只是說可以在DAG上使用,並不代表無向圖不能使用,也不能代表有環圖不能使用。從迪傑斯特拉的算法原理上來說,無向圖是沒有問題的,只需要給matrix[source][target]和matrix[target][source]賦上相同的權值,因爲它每次只會根據到源點的距離,選取距離最近的一個節點加入,所以有沒有方向都無所謂,算法只關注可達點的距離;至於有環圖,它對每個節點的距離計算只用了一層遍歷去做,並不會陷入死循環,也不會出現重複計算的問題。因此迪傑斯特拉算法是可以用在無向圖和有環圖中的,適合於求單源最短路徑。

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