1. 程式人生 > >用鄰接表和最小堆實現Dijkstra 最短路演算法 (Java實現)

用鄰接表和最小堆實現Dijkstra 最短路演算法 (Java實現)

  • 演算法特點:

    迪科斯徹演算法使用了廣度優先搜尋解決賦權有向圖或者無向圖的單源最短路徑問題,演算法最終得到一個最短路徑樹。該演算法常用於路由演算法或者作為其他圖演算法的一個子模組。

  • 演算法的思路

    Dijkstra演算法採用的是一種貪心的策略,宣告一個數組dis來儲存源點到各個頂點的最短距離和一個儲存已經找到了最短路徑的頂點的集合:T,初始時,原點 s 的路徑權重被賦為 0 (dis[s] = 0)。若對於頂點 s 存在能直接到達的邊(s,m),則把dis[m]設為w(s, m),同時把所有其他(s不能直接到達的)頂點的路徑長度設為無窮大。初始時,集合T只有頂點s。 
    然後,從dis陣列選擇最小值,則該值就是源點s到該值對應的頂點的最短路徑,並且把該點加入到T中,OK,此時完成一個頂點, 
    然後,我們需要看看新加入的頂點是否可以到達其他頂點並且看看通過該頂點到達其他點的路徑長度是否比源點直接到達短,如果是,那麼就替換這些頂點在dis中的值。 
    然後,又從dis中找出最小值,重複上述動作,直到T中包含了圖的所有頂點。

      對於鄰接表不太熟悉的可以看下面這個連結,鄰接表

下面直接簡單粗暴貼程式碼

import java.util.Comparator;
import java.util.PriorityQueue;

/*
使用鄰接表和優先佇列(小根堆)的D演算法
頂點從1開始
*/
public class Dijkstra {
    int n;//點數
    int m;//邊數
    //鄰接表
    private int[] U; //第i條邊的起點
    private int[] V; //第i條邊的終點
    private int[] W; //第i條邊的長度
    private int[] first; //起點為i的第一條邊
    private int[] next; //起點為i的下一條邊,-1表示沒了


    class distanceTo {
        int des;
        int distance;

        distanceTo(int des, int distance) {
            this.des = des;
            this.distance = distance;
        }
    }

    Dijkstra(int n, int m, int[][] edges) { //點數,邊數,輸入陣列(起點,終點,長度)
        if (m != edges.length)
            throw new IllegalArgumentException();
        U = new int[m + 1];
        V = new int[m + 1];
        W = new int[m + 1];
        first = new int[n + 1];
        next = new int[n + 1];
        for (int i = 0; i < edges.length; ++i) { //讀入每一條邊
            if (edges[i].length != 3)
                throw new IllegalArgumentException();
            U[i + 1] = edges[i][0];
            V[i + 1] = edges[i][1];
            W[i + 1] = edges[i][2];
        }
        for (int i = 1; i < first.length; ++i) {
            first[i] = -1;
        }
        for (int i = 1; i <= m; ++i) {
            next[i] = first[U[i]]; //鄰接表的關鍵
            first[U[i]] = i;
        }
    }

    public int[] findNearestPath(int s) { //源點i到其他點的最短路
        PriorityQueue<distanceTo> updatingDistance = new PriorityQueue<>(new Comparator<distanceTo>() {
            @Override
            public int compare(distanceTo o1, distanceTo o2) {
                return o1.distance - o2.distance;
            }
        });
        int[] distance = new int[n + 1];
        for (int i = 1; i < first.length; ++i) {
            if (i == s)
                continue;
            distance[i] = Integer.MAX_VALUE;
        }
        int[] path = new int[n + 1];

        int i = first[s];
        if (i == -1)
            return path;
        while (i != -1) { //遍歷s為源點的所有邊
            distance[V[i]] = W[i];
            updatingDistance.offer(new distanceTo(V[i], W[i]));
            i = next[i];
        }
        for (i = 1; i <= n; i++) { //因為鄰接表不會存不能直達的點
            if (distance[i] == Integer.MAX_VALUE)
                updatingDistance.offer(new distanceTo(i, distance[i]));
        }
        for (i = 1; i <= n - 1; ++i) { //D演算法要迴圈n-1次
            int t = first[updatingDistance.poll().des]; //當前到點w距離最短

            while (t != -1) {
                if (distance[V[i]] > distance[U[i]] + W[i]) {
                    distance[V[i]] = distance[U[i]] + W[i];
                    updatingDistance.offer(new distanceTo(V[i], distance[V[i]]));//不需要刪掉被覆蓋的,因為他們會被排在後面
                    path[V[i]] = U[i];
                }
            }
        }
        return path;
    }
}

最後返回的path陣列記錄了最短的路徑,比如path[5] = 3,表示到點5的最短路徑上一步是在點3,一直到某個值等於出發點,即得到完整路徑。