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

1. 應用場景:

假如有七個村莊(ABCDEFG),有個人從G點出發,到其他六個村莊的最短路徑分別是多少?到A、B、F、E只有一條路,沒得選,但是到C有兩條路,可以是2 + 7,也可以是8 + 4,到D點可以是3 + 9,也可以是6 + 4。圖上標明瞭距離我們當然一看就知道怎麼選,那麼如何能讓程序選擇最短的路徑呢?

迪傑斯特拉算法就是求最短路徑的經典算法。它的主要思想就是以起始點向外層層擴展,用廣度優先的思想,直到擴展到終點爲止。

2. 算法步驟:

以上面的例子,從G開始處理,來講解這個過程:

  • 首先我們會用一個數組或者集合來保存這7個頂點,如下:
String[] vertexs = {"A", "B", "C", "D", "E", "F", "G"};
  • 其次,會有一個鄰接矩陣來表示各個頂點之間的距離,如下(N表示聯不通,可以用一個很大的數表示):
  A  B  C  D  E  F  G
A{N, 5, 7 ,N, N, N, 2}
B{5, N, N ,9, N, N, 3}
C{7, N, N ,N, 8, N, N}
D{N, 9, N ,N, N, 4, N}
E{N, N, 8 ,N, N, 5, 4}
F{N, N, N ,4, 5, N, 6}
G{2, 3, N ,N, 4, 6, N}
  • 接着創建一個數組already_arr,長度爲頂點的個數。數組裏面,0表示未訪問過該頂點,1表示訪問過該頂點。初始的時候其他都是0,G頂點的是1。
int[] already_arr = {0, 0, 0, 0, 0, 0, 1};
  • 再創建一個數組dis,長度也是頂點的個數。該數組記錄的是當前頂點到其他頂點的距離,初始的時候是一個較大的數,到自己的距離就是0。
int[] dis = {N, N, N, N, N, N, 0};
  • 還要創建一個數組pre_visited,長度也是頂點的個數。數組表示頂點的到前驅頂點的距離,初始時候都是0。
int[] pre_visited = {0, 0, 0, 0, 0, 0, 0};
  • 處理了頂點G,即鄰接矩陣的最後一行,可以發現,G到A的距離是2,小於9999,到B的距離是3,也小於9999……所以更新dis數組,如下:
int[] dis = {2, 3, N, N, 4, 6, 0};
  • 從鄰接矩陣的最後一行可以看出,G和A、B、E、F是直接連通的,也就說明,ABEF這四個點的前驅頂點都是G,所以pre_visited數組變成了:
int[] pre_visited = {6, 6, 0, 0, 6, 6, 0};
  • 上面就是第一次的處理過程,然後按照廣度優先的方式,每訪問一個頂點,就按照上面的方式去修改這3個數組。最後的G頂點到每個頂點的最短距離就記錄中dis數組中。

3. 代碼實現:

  • 首先得把圖創建出來:
class Graph{
    String[] vertexs; // 存放頂點
    int[][] edges; // 鄰接矩陣,存放邊
    
    public Graph(String[] vertexs, int[][] edges) {
        this.vertexs = vertexs;
        this.edges = edges;
    }
}
  • 然後,創建一個類,類裏面有個三個屬性,就是上面說的那三個數組,以及一些必要的方法:
class VisitedVertex{
    
    private static final int N = 999;
    
    public int[] already_arr;
    public int[] pre_visited;
    public int[] dis;
    
    /**
     * 構造器
     * @param length 頂點的個數
     * @param index 出發點的索引
     */
    public VisitedVertex(int length, int index) {
        this.already_arr = new int[length];
        this.pre_visited = new int[length];
        this.dis = new int[length];
        // 初始化dis數組
        Arrays.fill(dis, N);
        dis[index] = 0;
    }
    
    /**
     * 判斷index對應的頂點是否被訪問過
     * @param index
     * @return
     */
    public boolean isVisited(int index) {
        return already_arr[index] == 1;
    }
    
    /**
     * 更新dis數組,索引爲index的值更新爲len
     * @param index
     * @param len
     */
    public void updateDis(int index, int len) {
        dis[index] = len;
    }
    
    /**
     * 更新前驅頂點
     * @param index
     * @param val
     */
    public void updatePre(int index, int val) {
        pre_visited[index] = val;
    }
    
/**
     * 將index設置爲已訪問
     * @param index
     */
    public void updateAlreadyArr(int index) {
        already_arr[index] = 1;
    }

    /**
     * 返回出發頂點到index這個頂點的距離
     * @param index
     * @return
     */
    public int getDis(int index) {
        return dis[index];
    }

    /**
     * 返回要處理的下一個頂點
     * @return
     */
    public int nextVertexs() {
        int index = 0;
        int min = N;
        for (int i=0; i< already_arr.length; i++) {
            if (!isVisited(i) && dis[i] < min) {
                min = dis[i];
                index = i;
            }
        }
        updateAlreadyArr(index);
        return index;
    }
    
    public void printArr() {
        System.out.println(Arrays.toString(this.dis) + "   dis");
        System.out.println(Arrays.toString(this.pre_visited) + "   pre_visited");
        System.out.println(Arrays.toString(this.already_arr) + "   already_arr");
    }
}
  • 那麼接下來就可以在Graph類中新增一個方法,來實現迪傑斯特拉算法了,如下:
    /**
     * 迪傑斯特拉算法實現
     * @param index 出發頂點的下標
     */
    public void dsj(int index) {
        VisitedVertex visitedVertex = new VisitedVertex(this.vertexs.length, index);
        updateArrs(index, visitedVertex);
        // 下一個要處理的頂點
        for (int i=1; i<vertexs.length; i++) {
            index = visitedVertex.nextVertexs();
            updateArrs(index, visitedVertex);
        }
        visitedVertex.printArr();
    }
    
    /**
     * 拿到鄰接矩陣的第index行,根據這一行對三個數組進行更新
     * @param index
     * @param visitedVertex
     */
    private void updateArrs(int index, VisitedVertex visitedVertex) {
        int[] tempArr = this.edges[index];
        // distance表示出發頂點到索引爲index的這個頂點的距離
        int distance = 0;
        for (int i=0; i< tempArr.length; i++) {
            // 先前的距離 + 本次遍歷得到的距離
            distance = visitedVertex.getDis(index) + tempArr[i];
            if (distance < visitedVertex.getDis(i) && !visitedVertex.isVisited(i)) {
                visitedVertex.updateDis(i, distance);
                visitedVertex.updatePre(i, index);
                visitedVertex.updateAlreadyArr(index);
            }
        }
    }

測試方法:

public class DijkstraDemo {
    
    public static final int N = 999;

    public static void main(String[] args) {
        String[] vertexs = {"A", "B", "C", "D", "E", "F", "G"};
        int[][] edges = {
            {N, 5, 7 ,N, N, N, 2},
            {5, N, N ,9, N, N, 3},
            {7, N, N ,N, 8, N, N},
            {N, 9, N ,N, N, 4, N},
            {N, N, 8 ,N, N, 5, 4},
            {N, N, N ,4, 5, N, 6},
            {2, 3, N ,N, 4, 6, N}
        };
        Graph graph = new Graph(vertexs, edges);
        graph.dsj(6);
    }
}

運行後得到結果如下:


dis數組就是我們想要的結果,即對應的頂點G到各個點的最短距離。

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