Dijkstrala算法

文章出處:極客時間《數據結構和算法之美》-作者:王爭。該系列文章是本人的學習筆記。

Dijkstrala算法查找圖中從一個節點到另一個節點的最短路徑,輸出結果是最短路徑以及長度。算法執行的前提條件是權重不能是負數。

起始頂點記爲sid,目的節點記爲tid。

數組predecessor記錄了最短路徑上一個節點的前驅節點。例如predecessor[3]=5,就是從節點5到節點3的路徑最短。

節點Vertext,保存節點id、當前節點距離sid的最短路徑值。

優先隊列queue按照節點的距離排序,在堆頂的是距離值最小的節點。

每次處理一個節點node的時候,處理其每條邊,計算每條邊的目的節點vertexts[to].dist。如果vertexts[to].dist>vertexts[node.id].dist + 邊的權重,則表示有新的最短路徑,更新,並且將目的節點加入到隊列中。

因爲可能有多條邊達到同一個節點,所以隊列中可能有多個元素表示同一個節點,會有多餘的操作。解決此問題可以自己寫一個優先隊列,實現更新元素。參考代碼

import java.util.*;

public class Graph {
    //頂點數量
    private int v;
    //鏈接表
    private LinkedList<Edge> adjacency[];
    
    /**
     * 構造頂點數爲v的圖
     * @param v
     */
    public Graph(int v) {
        this.v = v;
        adjacency = new LinkedList[v];
        for(int i=0;i<v;i++){
            adjacency[i] =  new LinkedList<>();
        }
    }

    /**
     * 添加一條邊
     * @param sid
     *          源節點id
     * @param tid
     *          目的節點id
     * @param weight
     *          權重
     */
    public void addEdge(int sid, int tid,int weight){
        Edge edge = new Edge(sid,tid,weight);
        adjacency[sid].add(edge);
    }

    /**
     * 計算從sid到tid的最短路徑。返回值是經過路徑的節點id
     * @param sid
     * @param tid
     * @return
     */
    public List<Integer> dijkstrala(int sid ,int tid){
        Vertext[] vertexts = new Vertext[v];
        for(int i=0; i < v;i++){
            vertexts[i] = new Vertext(i);
        }
        vertexts[sid].dist = 0;

        //predecessor[i]=得到i的前驅節點
        int[] predecessor = new int[v];
        predecessor[sid] = -1;
        PriorityQueue<Vertext> queue = new PriorityQueue<>(this.v,
                new Comparator<Vertext>(){
                    public int compare(Vertext o1, Vertext o2){
                        return o1.dist - o2.dist;
                    }
                });
        //隊列中可能存在重複節點,會存在多餘操作
        queue.offer(vertexts[sid]);
        while(! queue.isEmpty()){
            Vertext node = queue.poll();
            if(node.id == tid) break;
            if(adjacency[node.id]!=null){
                for(Edge edge : adjacency[node.id]){
                    int to = edge.tid;
                    if(vertexts[node.id].dist + edge.weight < vertexts[to].dist){
                        vertexts[to].dist = vertexts[node.id].dist + edge.weight;
                        queue.add(vertexts[to]);
                        predecessor[to] = node.id;
                    }
                }
            }
        }
        List<Integer> path = new ArrayList<Integer>();
        if(vertexts[tid].dist < Integer.MAX_VALUE){
            visitPredecessor(predecessor,tid,path);
            Collections.reverse(path);
        }
        return path;
    }

    private void visitPredecessor(int[] predecessor, int tid,List<Integer> path) {
        path.add(tid);
        if(predecessor[tid] != -1){
            visitPredecessor(predecessor,predecessor[tid],path);
        }
    }

    private class Vertext{
        //編號
        private int id;
        //距離
        private int dist = Integer.MAX_VALUE;

        public Vertext(int id) {
            this.id = id;
        }
    }
    private class Edge{
        private int sid;
        private int tid;
        private int weight;

        public Edge(int sid, int tid, int weight) {
            this.sid = sid;
            this.tid = tid;
            this.weight = weight;
        }
    }
}

時間複雜度是:O(ElogV)。程序主體是一個while循環,裏面是一個for循環。因爲每次for循環次數不定,但是所有for循環執行次數之和不會超過E(邊的個數)。換句話說第一個執行E0、第二次執行E1、…第V次執行EV,E0+E1+…+Ev=E。(這裏先假設隊列中每個節點只有一個元素)
for循環裏面是隊列的入隊、出隊操作。假設隊列中每個節點只有一個元素,那麼隊列不會超過V。所以入隊、出隊操作時間複雜度logV。那麼總的時間複雜度是O(E
logV)。

隊列中有重複元素,隊列的最大不會超過E。那入隊、出隊操作時間複雜度是logE。E<=V2E<=V^2,logE<=2logVlogE<=2logV。總時間複雜度還是O(E*logV)這個級別。如果在稀疏圖中,這個時間複雜度是可以接受的。但在某些情況下會很糟糕。可以考慮在隊列中更新節點。參考代碼

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