1. 程式人生 > 其它 >圖解迪傑斯特拉演算法(最短路徑問題)

圖解迪傑斯特拉演算法(最短路徑問題)

技術標籤:演算法與資料結構dijkstra迪傑斯特拉最短路徑問題java演算法

文章目錄

一、單源最短路徑問題

在這裡插入圖片描述

如上圖給定一個帶權圖 G = <V,E>,其中每條邊(vi,vj)上的權 W[vi,vj] 是一個非負實數。另外,給定 V 中的一個頂點 s 充當源點。現在要計算從源點 s 到所有其他各頂點的最短路徑,這個問題通常稱為單源最短路徑(single-source shortest paths

)問題。

用一句話總結來說,單源最短路徑就是:從圖的某一點(源點)出發,到達其餘各頂點(終點)的最短路徑

解決單源最短路徑問題的一個常用演算法是迪傑斯特拉演算法,它是由 E.W.Dijkstra 提出的一種按路徑長度遞增的次序產生到各頂點最短路徑的貪心演算法。

二、迪傑斯特拉演算法

2.1 什麼是迪傑斯特拉演算法

迪傑斯特拉(Dijkstra)演算法是典型最短路徑演算法,它用於計算一個頂點到其他頂點的最短路徑。它的主要特點是:以起始點為中心向外層層擴充套件(廣度優先搜尋思想), 直到擴充套件到終點

迪傑斯特拉的基本思想如下:

把圖的頂點集合劃分為兩個集合 S 和 V-S。第一個集合 S 表示距源點最短距離已經確定的頂點集,即一個頂點如果屬於集合 S 則說明從源點 s 到該頂點的最短路徑已知。其餘的頂點放在另一個集合 V-S 中。初始時,集合 S 只包含源點,即 S = { s },這時只有源點到自己的最短距離是已知的。設 v 是 V 中的某個頂點,把從源點 s 到頂點 v 且中間只經過集合 S 中頂點的路徑稱為從源點到 v 的特殊路徑

,並用陣列 D 來記錄當前所找到的從源點 s 到每個頂點的最短特殊路徑長度。從尚未確定最短路徑長度的集合 V-S 中取出一個最短特殊路徑長度最小的頂點 u,將 u 加入集合 S,同時修改陣列 D 中由 s 可達的最短路徑長度。

2.2 迪傑斯特拉演算法的步驟

2.2.1 基本步驟

迪傑斯特拉的基本步驟如下:

  1. 首先,定義一個數組 D,D[v] 表示從源點 s 到頂點 v 的邊的權值,如果沒有邊則將 D[v] 置為無窮大。
  2. 把圖的頂點集合劃分為兩個集合 S 和 V-S。第一個集合 S 表示距源點最短距離已經確定的頂點集,即一個頂點如果屬於集合 S 則說明從源點 s 到該頂點的最短路徑已知。其餘的頂點放在另一個集合 V-S 中。
  3. 每次從尚未確定最短路徑長度的集合 V-S 中取出一個最短特殊路徑長度最小的頂點 u,將 u 加入集合 S,同時修改陣列 D 中由 s 可達的最短路徑長度。若加入集合 S 的 u 作為中間頂點時,vi 的最短路特殊路徑長度變短,則修改 vi 的距離值(即當D[u] + W[u, vi] < D[vi]時,令D[vi] = D[u] + W[u, vi])。
  4. 重複第 3 步的操作,一旦 S 包含了所有 V 中的頂點,D 中各頂點的距離值就記錄了從源點 s 到該頂點的最短路徑長度。

整個演算法最核心的步驟就是第 3 步,可以將其總結為:每次從 V-S 集合中取出距離源點最近的頂點 u 加入到 S 集合中,然後更新從源點經過 u 到達各個頂點的距離

2.2.2 圖解演示

如下圖所示,以 v0 為起點,演示使用迪傑斯特拉演算法求解其到各個頂點的最短路徑。

在這裡插入圖片描述

圖解過程如下:

  1. 定義一個數組 dis,dis[i] 表示從源點 v0 到頂點 vi 的邊的權值,比如這裡 dis[1] = 7。初始狀態下,dis 各個元素為 0。

  2. 定義一個集合 S 用於存放距源點最短距離已經確定的頂點。初始狀態下,S = { v0 }。

    在這裡插入圖片描述

  3. 首先,以 v0 為源點,到 v1 的距離是 7,到 v2 的距離是 3,到終點的距離暫不清楚,因此假設為 ∞。此時 dis 為:

    在這裡插入圖片描述

  4. 選擇最短的一條作為確定找到的最短路徑。由上表可知,在 V-S 頂點集合中,v2 和源點的距離最短,因此將 v2 加入到 S 集合中。此時 S = { v0,v2 }。

    在這裡插入圖片描述

  5. 接下來我們看 V-S 中與 v2 連線的點,分別有 v3,v1。由於 dis[v2]+dis[v2_v1] < dis[v1],故將 dis[v1] 更新為 dis[v2]+dis[v2_v1];基於同樣的理由,dis[v3] 更新為 dis[v2] + dis[v2_v3]。此時的 dis 為:

    在這裡插入圖片描述

  6. 在 V-S 的頂點集合中,v1 和源點的距離最短,因此將 v1 加入到 S 集合中,此時 S = { v0,v2,v1 }。

    在這裡插入圖片描述

  7. 接下來我們再看 V-S 中與 v1 連線的點,只有 v3 。由於 dis[v1] + dis[v1_v3] < dis[v3],因此將 dis[v3] 更新為 dis[v1] + dis[v1_v3]。此時的 dis 為:

    在這裡插入圖片描述

  8. 再從 V-S 集合中選擇距離源點最近的頂點,目前 V-S 集合中只有 v3,因此將 v3 加入到 S 集合中。此時 S = {v0,v2,v1,v3},演算法執行完畢。

    在這裡插入圖片描述

  9. 演算法執行結束後,dis 陣列中的值就記錄了從源點到各個頂點的最短距離。

2.3 迪傑斯特拉演算法的程式碼實現

public class DijkstraAlgorithm {

    private final static int N = 10000;     // 約定 10000 代表距離無窮大

    public static void main(String[] args) {
        char[] vertexes = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };    // 頂點
        int[][] weight = {  	// 圖的鄰接矩陣
                    /*A*//*B*//*C*//*D*//*E*//*F*//*G*/
                /*A*/{0,   5,   7,   N,   N,   N,   2},
                /*B*/{5,   0,   N,   9,   N,   N,   3},
                /*C*/{7,   N,   0,   N,   8,   N,   N},
                /*D*/{N,   9,   N,   0,   N,   4,   N},
                /*E*/{N,   N,   8,   N,   0,   5,   4},
                /*F*/{N,   N,   N,   4,   5,   0,   6},
                /*G*/{2,   3,   N,   N,   4,   6,   0}
        };

        int source = 6; // 源點下標
        int[] dis = dijkstra(source, vertexes, weight);	// 使用迪傑斯特拉查詢最短路徑

        // 輸出最短路徑長度
        for (int i=0; i<dis.length; i++){
            System.out.println(vertexes[source] + "->" + vertexes[i] + " = " + dis[i]);
        }
    }

    /**
     * 迪傑斯特拉演算法求解最短路徑問題
     * @param source    源點下標
     * @param vertexes  頂點集合
     * @param weight    鄰接矩陣
     * @return int[]    源點到各頂點最短路徑距離
     */
    public static int[] dijkstra(int source, char[] vertexes, int[][] weight){
        int[] dis;     // 記錄源點到各頂點的最短路徑長度,如 dis[2] 表示源點到下標為 2 的頂點的最短路徑長度
        ArrayList<Character> S = new ArrayList<>(); // 儲存已經求出到源點最短路徑的頂點,即演算法步驟中的 S 集合。

        /* 初始化源點 */
        dis = weight[source];
        S.add(vertexes[source]);

        /* 當 S 集合元素個數等於頂點個數時,說明最短路徑查詢完畢 */
        while(S.size() != vertexes.length){
            int min = N;
            int index = -1; // 記錄已經求出最短路徑的頂點下標

            /* 從 V-S 的集合中找距離源點最近的頂點 */
            for (int j=0; j<weight.length; j++){
                if (!S.contains(vertexes[j]) && dis[j] < min){
                    min = weight[source][j];
                    index = j;
                }
            }
            dis[index] = min;   // 更新源點到該頂點的最短路徑長度
            S.add(vertexes[index]); // 將頂點加入到 S 集合中,即表明該頂點已經求出到源點的最小路徑

            /* 更新源點經過下標為 index 的頂點到其它各頂點的最短路徑 */
            for (int m=0; m<weight.length; m++){
                if (!S.contains(vertexes[m]) && dis[index] + weight[index][m] < dis[m]){
                    dis[m] = dis[index] + weight[index][m];
                }
            }
        }
        return dis;
    }
}