单源最短路径问题之dijkstra算法
欢迎探讨,如有错误敬请指正
如需转载,请注明出处 http://www.cnblogs.com/nullzx/
1. 算法的原理
以源点开始,以源点相连的顶点作为向外延伸的顶点,在所有这些向外延伸的顶点中选择距源点最近的顶点(如果有多个距离最近的顶点,任意选择一个即可)继续向四周延伸(某个顶点被选作继续延伸的顶点,则源点到它的最短距离就已经确定,我们也不再将其视为向外延伸的顶点了),如果在继续延伸的过程中遇到了之前已延伸到的顶点,且当前这次延伸过程使其离源点更近,我们就修正这个距离,直到所有的顶点都被当做过继续延伸的顶点,此时我们就得到了源点到其它各个顶点的距离。
2. 一个具体的例子
在下面的例子中,模拟了dijkstra算法求解顶点3到其它各个顶点的最短距离。
黑色的顶点表示没有被延伸到的顶点,此时源点到它的距离为无穷。红色顶点表示已被延伸到的顶点,红色顶点旁的数字表示源点到它的距离。绿色顶点表示源点到该顶点的最短距离已确定。如果源点到某个顶点的距离被修正,我们将用黄色的方框将其标注。
distTo数组表示源点(下图中源点为顶点3)到各个顶点的距离,其中绿色的表示最短距离,红色表示这个距离是否是最短距离还未确定。
edgeTo数组表示源点(下图中源点为顶点3)到各个顶点的路径,其中绿色的表示最短距离路径已确定,红色表示这个路径是否是最路径离还未确定。edgeTo[i]表示经过顶点i的上一个顶点。
初始时,将源点看做向外延伸的顶点,它到自身的距离为0.0。每次向外延伸的过程中我们都会从红色的顶点中选择距离最近的顶点(如果距离最近的顶点有多个,则任意选择一个)继续向外延伸,然后把该顶点标注成绿色。
下面是源点到各个节点的最短路径
最后我们从distTo和edgeTo数组就可以找到最短的距离和路径。
假设我们想得到源点,即顶点3,到顶点1的距离和路径。
distTo[1]的值为5.0,说明最短距离为5.0。
edgeTo[1]说明到顶点1的上一个顶点为顶点10,edgeTo[10]说明到顶点10的上一个顶点为4,edgeTo[4]说明到顶点4的上一个顶点为顶点0,edgeTo[0]说明到顶点0的上一个顶点为顶点3。也就是路径为3->0->4->10->1
3. 算法的证明
要证明算法的正确性,我们实际上需要证明:当从延伸顶点中选择离源点最近的顶点继续向外延伸时,源点到它的最短距离就已经确定了。
假设当前延伸顶点中最短的距离的顶点t,同时存在一条路径从已访问到的顶点k经过某些未知顶点x到达t的距离更小。
已知distTo[s->t] <= distTo[s->k],
如果distTo[k->x]以及distTo[x->t]都大于零, 那么
distTo[s->t] < distTo[s->k] + distTo[k->x] + distTo[x->t]
上述不等式必然成立,所以算法的正确性得以证明。不等式成立的充分条件是distTo[k->x]以及distTo[x->t]都大于零,这也使我们得出了Dijistra算法的适用情况:边的权值都为非负值。
显然,如果是求给定两点s到t的最短距离,我们只需要在延伸的过程中将t作为继续向四周延伸的顶点时停止算法即可。
4. 算法的实现
测试数据
8 15 4 5 0.35 5 4 0.35 4 7 0.37 5 7 0.28 7 5 0.28 5 1 0.32 0 4 0.38 0 2 0.26 7 3 0.39 1 3 0.29 2 7 0.34 6 2 0.40 3 6 0.52 6 0 0.58 6 4 0.93 |
输出结果
0 : [4 , 0.38] [2 , 0.26] 1 : [3 , 0.29] 2 : [7 , 0.34] 3 : [6 , 0.52] 4 : [5 , 0.35] [7 , 0.37] 5 : [4 , 0.35] [7 , 0.28] [1 , 0.32] 6 : [2 , 0.40] [0 , 0.58] [4 , 0.93] 7 : [5 , 0.28] [3 , 0.39] 1.51 0 2 0.26 2 7 0.34 7 3 0.39 3 6 0.52 |
源代码
package datastruct; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.PriorityQueue; import datastruct.Graph.Edge; public class Dijkstra { private double[] distTo; private Edge[] edgeTo; private PriorityQueue<DistTo> pq; private Graph g; private int s; //源点到顶点V的距离, public static class DistTo implements Comparable<DistTo>{ public int idx; //顶点的编号 public double dist;//源点到顶点idx的短距离 public DistTo(int v, double totalDist){ this.idx = v; this.dist = totalDist; } @Override public int compareTo(DistTo that) { if(this.dist > that.dist){ return 1; }else if(this.dist < that.dist){ return -1; }else{ return 0; } } } public Dijkstra(Graph g, int s){ this.g = g; this.s = s; shortPath(); } private void shortPath(){ edgeTo = new Edge[g.V()]; distTo = new double[g.V()]; Arrays.fill(distTo, Double.POSITIVE_INFINITY); distTo[s] = 0.0; //如果到源点到顶点v的距离被多次修正,那么优先队列中就可能存在到顶点v的多个距离 //所以以边的个数作为优先队列的最大长度,使用索引优先队列是个更好的选择 pq = new PriorityQueue<DistTo>(g.E()); pq.offer(new DistTo(s, 0.0)); while(!pq.isEmpty()){ DistTo v = pq.poll();//每次从优先队列中找到最近的延伸顶点 for(Edge e : g.adjEdge(v.idx)){//从与idx顶点的出边继续向下延伸 int to = e.to(); if(v.dist + e.weight() < distTo[to]){ edgeTo[to] = e;//修正路径 distTo[to] = v.dist + e.weight();//修正距离 pq.offer(new DistTo(to, distTo[to]));//修正后入列 } } } } public double shortDistTo(int v){ return distTo[v]; } public List<Edge> shortPathTo(int v){ LinkedList<Edge> stack = new LinkedList<Edge>(); int to = v; if(distTo[to] == Double.POSITIVE_INFINITY){ return stack; } while(edgeTo[to] != null){ stack.push(edgeTo[to]); to = edgeTo[to].from(); } return stack; } public static void main(String[] args) throws FileNotFoundException{ File path = new File(System.getProperty("user.dir")).getParentFile(); File f = new File(path, "algs4-data/tinyEWD.txt"); FileReader fr = new FileReader(f); Graph g = new Graph(fr, true, true); System.out.println(g); Dijkstra dijkstra = new Dijkstra(g, 0); System.out.printf("%.2f\n", dijkstra.shortDistTo(6)); List<Edge> shortPath = dijkstra.shortPathTo(6); for(Edge e : shortPath){ System.out.println(e); } } }
有关Graph类的实现可参照我技术博客的另一篇文章:
5. 参考内容
[1]. 算法(第4版)Robert Sedgewick 人民邮电出版社