數據結構(六)——圖經典算法之Dijkstra算法

Dijkstra算法不僅是圖的經典算法,同時也是對貪心算法比較好的一個實例。

Dijkstra算法

通過Dijkstra計算圖G中的最短路徑時,需要指定起點vs(即從頂點vs開始計算)。

此外,引進兩個集合S和US的作用是記錄已求出最短路徑的頂點,而U則是記錄還未求出最短路徑的頂點(以及該頂點到起點vs的距離)。

算法步驟

(1) 初始時,S只包含起點vs;U包含除vs外的其他頂點,且U中頂點的距離爲"起點vs到該頂點的距離"[例如,U中頂點v的距離爲(vs,v)的長度,然後vs和v不相鄰,則v的距離爲∞]。

(2) 從U中選出"距離最短的頂點k",並將頂點k加入到S中;同時,從U中移除頂點k。

(3) 更新U中各個頂點到起點vs的距離。之所以更新U中頂點的距離,是由於上一步中確定了k是求出最短路徑的頂點,從而可以利用k來更新其它頂點的距離;例如,(vs,v)的距離可能大於(vs,k)+(k,v)的距離。

(4) 重複步驟(2)和(3),直到遍歷完所有頂點。

實例

在這裏插入圖片描述

後續代碼會以這個爲實例,這裏先貼出這個實例,這裏爲了理解方便,不一次性貼出代碼,而是層層解構,最後進行彙總

初始化數據

//初始化
boolean[] flag = new boolean[vertexes.length];	//用於判斷是否已經被遍歷的標示
int[] U = new int[vertexes.length];	//集合U,記錄到各個點的距離
String[] S = new String[vertexes.length];	//集合S,已經計算完成的節點集合
int[] prev = new int[vertexes.length];	//用於記錄路徑的數組,just 記錄而已

//vs表示起始節點的索引,初始化U爲vs節點的所有邊的權值,flag均爲false
for (int i = 0; i < vertexes.length; i++) {
    flag[i] = false;
    U[i] = matrix[vs][i];
    prev[i] = 0;
}

起始節點的處理

//起始節點的處理
S[0] = vertexes[vs]; //起始節點進入S集合
flag[vs] = true;	//標記起始節點爲已訪問
U[vs] = 0;	//將U集合中,vs的權值置爲0,畢竟從自己到自己權值爲0;

核心的Dijkstra整體框架

for (int i = 0; i < vertexes.length; i++) {
	//1、找到當前U中最小的元素,並記錄下該元素的下標(編程入門難度的邏輯)
    
    //2、將第一步找到的節點加入集合S,並將其標記爲已經訪問
    
    //3、更新集合U
}

其實結構完成之後,上述步驟好像最難的就是第三步,放心,其實也不難,這裏就直接貼出第三步的代碼吧

int k = 0;
for (int i = 0; i < vertexes.length; i++) {
    //先找到當前U中最小的節點,並記錄下標
    int min = MAX_WEIGHT;
    for (int j = 0; j < vertexes.length; j++) {
        if (U[j] < min && flag[j] == false) {
            min = U[j];
            k = j;
        }
    }

    //找到的節點應該入S集合
    S[i] = vertexes[k];
    flag[k] = true;//同時標記該最小值的節點爲被訪問。

    //繼續更新集合U
    for (int j = 0; j < vertexes.length; j++) {
        //temp記錄爲當前的min+當前節點到其他可達節點的權值。
        //int temp = min + matrix[k][j];//直接這麼寫會產生位溢出,畢竟用到了Integer.MAX_VALUE
        int temp = matrix[k][j] == MAX_WEIGHT ? MAX_WEIGHT : (min + matrix[k][j]);

        //正式開始更新U集合
        if (flag[j] == false && temp < U[j]) {
            U[j] = temp;
            prev[j] = k; //記錄一下節點前驅下標。
        }
    }
}

打印路徑

//開始打印路徑
System.out.println("起始頂點:" + vertexes[vs]);
for (int i = 0; i < vertexes.length; i++) {
    System.out.print("最短路徑(" + vertexes[vs] + "," + vertexes[i] + "):" + U[i] + "  ");

    List<String> path = new ArrayList<>();
    int j = i;
    while (true) {
        path.add(vertexes[j]);
        if (j == 0) {
            break;
        }
        j = prev[j];
    }

    //完成打印工作
    for (int x = path.size() - 1; x >= 0; x--) {
        if (x == 0) {
            System.out.println(path.get(x));
        } else {
            System.out.print(path.get(x) + "->");
        }
    }
}

打印的邏輯不是很難,和走鏈一樣,只不是這裏是通過數組的下標而已。這裏就不解釋了,很簡單。

完整代碼

加上測試的代碼都在一起,可以直接運行的

import com.learn.graph.MatrixNDG;
import java.util.ArrayList;
import java.util.List;

/**
 * autor:liman
 * createtime:2020/2/14
 * comment:迪傑斯特拉算法
 */
public class ShorestPathDijkstraSelf {

    private int[][] matrix;

    private int MAX_WEIGHT = Integer.MAX_VALUE;

    private String[] vertexes;

    /**
     * 構建圖
     *
     * @param index
     */
    public void createGraph(int index) {
        matrix = new int[index][index];
        vertexes = new String[index];

        int[] v0 = {0, 1, 5, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT};
        int[] v1 = {1, 0, 3, 7, 5, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT};
        int[] v2 = {5, 3, 0, MAX_WEIGHT, 1, 7, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT};
        int[] v3 = {MAX_WEIGHT, 7, MAX_WEIGHT, 0, 2, MAX_WEIGHT, 3, MAX_WEIGHT, MAX_WEIGHT};
        int[] v4 = {MAX_WEIGHT, 5, 1, 2, 0, 3, 6, 9, MAX_WEIGHT};
        int[] v5 = {MAX_WEIGHT, MAX_WEIGHT, 7, MAX_WEIGHT, 3, 0, MAX_WEIGHT, 5, MAX_WEIGHT};
        int[] v6 = {MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 3, 6, MAX_WEIGHT, 0, 2, 7};
        int[] v7 = {MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 9, 5, 2, 0, 4};
        int[] v8 = {MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 7, 4, 0};

        matrix[0] = v0;
        matrix[1] = v1;
        matrix[2] = v2;
        matrix[3] = v3;
        matrix[4] = v4;
        matrix[5] = v5;
        matrix[6] = v6;
        matrix[7] = v7;
        matrix[8] = v8;

        vertexes[0] = "v0";
        vertexes[1] = "v1";
        vertexes[2] = "v2";
        vertexes[3] = "v3";
        vertexes[4] = "v4";
        vertexes[5] = "v5";
        vertexes[6] = "v6";
        vertexes[7] = "v7";
        vertexes[8] = "v8";
    }

    public void dijkstra(int vs) {
        //初始化
        boolean[] flag = new boolean[vertexes.length];
        int[] U = new int[vertexes.length];
        String[] S = new String[vertexes.length];
        int[] prev = new int[vertexes.length];

        for (int i = 0; i < vertexes.length; i++) {
            flag[i] = false;
            U[i] = matrix[vs][i];
            prev[i] = 0;
        }

        //起始節點的處理
        S[0] = vertexes[vs];
        flag[vs] = true;
        U[vs] = 0;

        int k = 0;
        for (int i = 0; i < vertexes.length; i++) {
            //先找到當前U中最小的節點,並記錄下標
            int min = MAX_WEIGHT;
            for (int j = 0; j < vertexes.length; j++) {
                if (U[j] < min && flag[j] == false) {
                    min = U[j];
                    k = j;
                }
            }

            //找到的節點應該入S集合
            S[i] = vertexes[k];
            flag[k] = true;//同時標記該最小值的節點爲被訪問。

            //繼續更新集合U
            for (int j = 0; j < vertexes.length; j++) {
//                int temp = min + matrix[k][j];//直接這麼寫會產生位溢出,畢竟用到了MAX_VALUE
                int temp = matrix[k][j] == MAX_WEIGHT ? MAX_WEIGHT : (min + matrix[k][j]);

                //正式開始更新U集合
                if (flag[j] == false && temp < U[j]) {
                    U[j] = temp;
                    prev[j] = k;
                }
            }
        }

        //開始打印路徑
        System.out.println("起始頂點:" + vertexes[vs]);
        for (int i = 0; i < vertexes.length; i++) {
            System.out.print("最短路徑(" + vertexes[vs] + "," + vertexes[i] + "):" + U[i] + "  ");

            List<String> path = new ArrayList<>();
            int j = i;
            while (true) {
                path.add(vertexes[j]);
                if (j == 0) {
                    break;
                }
                j = prev[j];
            }

            //完成打印工作
            for (int x = path.size() - 1; x >= 0; x--) {
                if (x == 0) {
                    System.out.println(path.get(x));
                } else {
                    System.out.print(path.get(x) + "->");
                }
            }
        }

        //打印一遍S集合
        System.out.println("頂點放入到S中的順序");
        for (int i = 0; i < vertexes.length; i++) {
            System.out.print(S[i]);
            if (i != vertexes.length - 1) {
                System.out.print("-->");
            }
        }

    }

    public static void main(String[] args) {
        ShorestPathDijkstraSelf shorestPathDijkstra = new ShorestPathDijkstraSelf();
        shorestPathDijkstra.createGraph(9);
        shorestPathDijkstra.dijkstra(0);
    }

}

運行結果:
在這裏插入圖片描述

總結

難嗎?貌似不太難啊。貪心算法的經典實例

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