算法:Dijkstra算法

算法概要:
解決(不含負權邊的加權有向圖的)單點最短路徑問題。計算的結果是一棵最短路徑樹(SPT,最短路徑樹)。主要特點是以起始點爲中心向外層層擴展,直到擴展到終點爲止。

維基百科的描述:
這個算法是通過爲每個頂點 v 保留目前爲止所找到的從s到v的最短路徑來工作的。初始時,原點 s 的路徑長度值被賦爲 0 (d[s] = 0),若存在能直接到達的邊(s,m),則把d[m]設爲w(s,m),同時把所有其他(s不能直接到達的)頂點的路徑長度設爲無窮大,即表示我們不知道任何通向這些頂點的路徑(對於 V 中所有頂點 v 除 s 和上述 m 外 d[v] = ∞)。當算法結束時,d[v] 中存儲的便是從 s 到 v 的最短路徑,或者如果路徑不存在的話是無窮大。 Dijkstra 算法的基礎操作是邊的拓展:如果存在一條從 u 到 v 的邊,那麼從 s 到 v 的最短路徑可以通過將邊(u, v)添加到尾部來拓展一條從 s 到 v 的路徑。這條路徑的長度是 d[u] + w(u, v)。如果這個值比目前已知的 d[v] 的值要小,我們可以用新值來替代當前 d[v] 中的值。拓展邊的操作一直運行到所有的 d[v] 都代表從 s 到 v 最短路徑的花費。這個算法經過組織因而當 d[u] 達到它最終的值的時候每條邊(u, v)都只被拓展一次。

算法維護兩個頂點集 S 和 Q。集合 S 保留了我們已知的所有 d[v] 的值已經是最短路徑的值頂點,而集合 Q 則保留其他所有頂點。集合S初始狀態爲空,而後每一步都有一個頂點從 Q 移動到 S。這個被選擇的頂點是 Q 中擁有最小的 d[u] 值的頂點。當一個頂點 u 從 Q 中轉移到了 S 中,算法對每條外接邊 (u, v) 進行拓展。


實現描述:
其實Dijkstra算法跟Prim算法有很多相似之處。
Prim算法每次選擇離MST距離最短的一個頂點,判斷是否要將它加入MST中。Dijkstra算法每次選擇離初始節點距離最短的一個頂點,判斷是否要將它加入SPT中。這一點在代碼中的體現就是relax方法
SP方法:**Dijkstra算法選取不同的初始節點,生成的SPT也不一樣。所以我實現**SP方法的時候提供了一個形參。每次調用SP方法,它都會先清除上一次的結果。
relax方法:遍歷從頂點v指出的所有邊(指向Wi),如果加入該邊後從初始節點(s)到Wi的距離可以變得更短,則暫時把它加入SPT中。


測試用的圖:
這裏寫圖片描述

package ShortestPath;

import java.util.Stack;
import list.IndexMinPQ;

public class Dijkstra {

    private EdgeWeightDiGraph g;//加權有向圖
    private int s;//初始節點
    private int[] from;
    private double[] dist;//索引位置的頂點距離初始節點的距離
    private IndexMinPQ<Double> pq;


    public Dijkstra(EdgeWeightDiGraph g, int s) {
        this.g = g;
        this.s = s;
        from = new int[g.V()];
        dist = new double[g.V()];
        for(int v=0;v<g.V();v++)
            dist[v] = Double.POSITIVE_INFINITY;
        pq = new IndexMinPQ<>(g.V());

        sp(s);//使用Dijkstra生成最短路徑樹
    }

    /**
     * 從頂點startVertex開始生成最短路徑樹
     * @param startVertex
     */
    public void sp(int startVertex){
        clear();//清除之前的最短路徑樹的信息
        this.s = startVertex;//更新初始節點
        dist[startVertex] = 0.0;
        pq.insert(startVertex, 0.0);
        while(!pq.isEmpty())
            relax(pq.delMin());
    }

    /**
     * 放鬆頂點v,並將放鬆後的結果插入索引優先隊列pq
     * @param v
     */
    public void relax(int v){
        for(DirectedEdge e:g.adj(v)){//遍歷從v指出的邊
            int w = e.to();
            if(dist[w] > dist[v] + e.weight()){//如果有更短路徑,則更新from和dist
                dist[w] = dist[v] + e.weight();
                from[w] = v;
                if(pq.contains(w))//然後更新pq
                    pq.changeKey(w, dist[w]);
                else
                    pq.insert(w, dist[w]);
            }
        }
    }

    /**
     * 清除之前的最短路徑樹的信息
     */
    private void clear(){
        from = new int[g.V()];
        dist = new double[g.V()];
        for(int v=0;v<g.V();v++)
            dist[v] = Double.POSITIVE_INFINITY;
        pq = new IndexMinPQ<>(g.V());
    }




    public double distTo(int w){
        return dist[w];
    }

    public boolean hasPathTo(int w){
        return dist[w] != Double.POSITIVE_INFINITY;
    }

    public Iterable<Integer> pathTo(int w){
        if(!hasPathTo(w))
            return null;
        Stack<Integer> path = new Stack<>();
        for(int x=w; x!=s; x=from[x])
            path.push(x);
        path.push(s);
        return path;
    }




    public static void main(String[] args) {
        EdgeWeightDiGraph g =new EdgeWeightDiGraph(8);

        DirectedEdge e1 = new DirectedEdge(4, 5, 0.35);
        DirectedEdge e2 = new DirectedEdge(5, 4, 0.35);
        DirectedEdge e3 = new DirectedEdge(4, 7, 0.37);
        DirectedEdge e4 = new DirectedEdge(5, 7, 0.28);
        DirectedEdge e5 = new DirectedEdge(7, 5, 0.28);
        DirectedEdge e6 = new DirectedEdge(5, 1, 0.32);
        DirectedEdge e7 = new DirectedEdge(0, 4, 0.38);
        DirectedEdge e8 = new DirectedEdge(0, 2, 0.26);
        DirectedEdge e9 = new DirectedEdge(7, 3, 0.39);
        DirectedEdge e10 = new DirectedEdge(1, 3, 0.29);
        DirectedEdge e11 = new DirectedEdge(2, 7, 0.34);
        DirectedEdge e12 = new DirectedEdge(6, 2, 0.40);
        DirectedEdge e13 = new DirectedEdge(3, 6, 0.52);
        DirectedEdge e14 = new DirectedEdge(6, 0, 0.58);
        DirectedEdge e15 = new DirectedEdge(6, 4, 0.93);
        g.addEdge(e1 );
        g.addEdge(e2 );
        g.addEdge(e3 );
        g.addEdge(e4 );
        g.addEdge(e5 );
        g.addEdge(e6 );
        g.addEdge(e7 );
        g.addEdge(e8 ); 
        g.addEdge(e9 );
        g.addEdge(e10);
        g.addEdge(e11);
        g.addEdge(e12);
        g.addEdge(e13);
        g.addEdge(e14);
        g.addEdge(e15);

        Dijkstra d = new Dijkstra(g,0); 

        Stack<Integer> stack = new Stack<>();

        stack = (Stack<Integer>) d.pathTo(6);

        System.out.println("頂點" + "\t" + "距離");
        while(!stack.isEmpty()){
            int a = stack.pop();
            System.out.print(a + "\t");
            System.out.println(d.distTo(a));
        }
    }

}

結果:
這裏寫圖片描述

總結:
在一幅含有V個頂點和E條邊的加權有向圖中,使用Dijkstra算法計算SPT的空間複雜度爲O(v), 時間複雜度爲O(ElogV)

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