算法: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)

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