Dijkstra’s algorithm

迪杰斯特拉算法是目前已知的解决单源最短路径问题的最快算法.

单源(single source)最短路径,就是从一个源点出发,考察它到任意顶点所经过的边的权重之和为最小的路径.

迪杰斯特拉算法不能处理权值为负数或为零的边,因为本质上它是一种贪心算法,出现了负数意味着它可能会舍弃一条正确的边,而选择一个长边和一个负数边,因为长边和负数边的权值之和可能小于那条正确的边.

算法描述

它的过程也很简单,按照广度遍历的方式考察每一条有向边(v,w),如果可以对边进行松弛,就记录这条边,并更新到边的目标顶点的最短距离.

注意,这个“最短距离”是当前搜索进程中已知的最短距离而不一定是最终的最短距离.

对边进行松弛的操作如下:

  1. /**
  2. * 对边进行松弛
  3. *
  4. * 对一条有向边(v,w,weight)进行考察:
  5. * 如果当前已知的到w的距离distance_to(w) > distance_to(v) + weight,
  6. * 说明可以改善到w的距离.从而更新这个距离:
  7. * distance_to(w) = distance_to(v) + weight;
  8. * 同时选择这条边为到w的目前已知的最短边
  9. * last_edge_to(w) = (v,w,weight)
  10. *
  11. * @param edge 有向带权边
  12. */
  13. private void relaxEdge(WeightedEdge edge) {
  14. IndexPriorityQueue<Double> q = indexCrossingEdges;
  15. int src = edge.src;
  16. int to =edge.to;
  17. if(distanceTo[to] > distanceTo[src] + edge.weight) {
  18. distanceTo[to] = distanceTo[src] + edge.weight;
  19. lastEdgeTo[to] = edge;
  20. if(q.contains(to))
  21. q.decreaseKey(to,distanceTo[to]);
  22. else
  23. q.offer(to,distanceTo[to]);
  24. }
  25. }

松弛操作中,使用了索引式优先队列来获取平均O(1)的插入效率和O(logN)的降权(这里是最小优先队列,所以decrease-key是提升优先级)效率,请参考:索引式优先队列

控制算法的搜索方向的是一个循环,这个循环考察队列是否为空以判断是否所有的边都得到了处理.

同时,在下面的代码中也可以看出,搜索的方向总是从源点s出发,遍历它的邻接表,当耗尽邻接表时,从优先队列中出队和它最近的邻接点v,接着对v的邻接表进行搜索.

所以整个搜索方向看上去是在向广度方向进行的.

  1. while (!q.isEmpty()) {
  2. src = q.poll();
  3. for(Edge e:g.vertices()[src].Adj) {
  4. relaxEdge((WeightedEdge)e);
  5. }
  6. }

可以看出,迪杰斯特拉算法和求最小生成树的普利姆算法非常相似,因为它们都是一种基于广度优先的贪心算法.

关于普利姆算法的分析和实现,请参考:说说最小生成树

实现代码

下面给出迪杰斯特拉的完整实现:

  1. /**
  2. * Created by 浩然 on 4/21/15.
  3. */
  4. public class Dijkstra {
  5. /**
  6. * 当前已知的最短距离,索引为顶点的编号
  7. * 比如distanceTo[v]表示当前到顶点v的最短距离
  8. */
  9. protected double[] distanceTo;
  10. /**
  11. * 当前已知的最短边,索引为顶点的编号
  12. * 比如lastEdgeTo[v]表示当前到顶点v的最短边
  13. */
  14. protected WeightedEdge[] lastEdgeTo;
  15. /**
  16. * 索引式优先队列,维护到顶点的最短距离
  17. */
  18. protected IndexPriorityQueue<Double> indexCrossingEdges;
  19. /**
  20. * 有向带权图
  21. */
  22. private WeightedDirectedGraph g;
  23. private LinkedList<WeightedEdge> shortestPath;
  24. /**
  25. * 单源
  26. */
  27. private int src;
  28. public Dijkstra(WeightedDirectedGraph g){
  29. this.g = g;
  30. }
  31. public void performSP(int src) {
  32. this.src = src;
  33. validateEdges();
  34. resetMemo();
  35. IndexPriorityQueue q = indexCrossingEdges;
  36. //从源点开始
  37. distanceTo[src] = 0;
  38. q.offer(src,distanceTo[src]);
  39. while (!q.isEmpty()) {
  40. src = q.poll();
  41. for(Edge e:g.vertices()[src].Adj) {
  42. relaxEdge((WeightedEdge)e);
  43. }
  44. }
  45. }
  46. /**
  47. * 对边进行松弛
  48. *
  49. * 对一条有向边(v,w,weight)进行考察:
  50. * 如果当前已知的到w的距离distance_to(w) > distance_to(v) + weight,
  51. * 说明可以改善到w的距离.从而更新这个距离:
  52. * distance_to(w) = distance_to(v) + weight;
  53. * 同时选择这条边为到w的目前已知的最短边
  54. * last_edge_to(w) = (v,w,weight)
  55. *
  56. * @param edge 有向带权边
  57. */
  58. private void relaxEdge(WeightedEdge edge) {
  59. IndexPriorityQueue<Double> q = indexCrossingEdges;
  60. int src = edge.src;
  61. int to =edge.to;
  62. if(distanceTo[to] > distanceTo[src] + edge.weight) {
  63. distanceTo[to] = distanceTo[src] + edge.weight;
  64. lastEdgeTo[to] = edge;
  65. if(q.contains(to))
  66. q.decreaseKey(to,distanceTo[to]);
  67. else
  68. q.offer(to,distanceTo[to]);
  69. }
  70. }
  71. private void validateEdges() {
  72. for(Vertex v:g.vertices()) {
  73. for(Edge e:v.Adj) {
  74. if(((WeightedEdge) e).weight < 0) {
  75. throw new IllegalArgumentException("边的权值不能为负!");
  76. }
  77. }
  78. }
  79. }
  80. private void resetMemo() {
  81. int vertexCount = g.vertexCount();
  82. //重置sp
  83. shortestPath = new LinkedList<>();
  84. //重置所有已知最短路径
  85. lastEdgeTo = new WeightedEdge[vertexCount];
  86. //重置所有距离
  87. distanceTo = new double[vertexCount];
  88. for(int i = 0; i < distanceTo.length; i++) {
  89. distanceTo[i] = Double.POSITIVE_INFINITY;
  90. }
  91. //重置优先队列
  92. indexCrossingEdges = new IndexPriorityQueue<>();
  93. }
  94. /**
  95. * 从源点到目标点是否存在一条路径
  96. * @param to 目标点
  97. * @return 存在则返回真,否则返回假
  98. */
  99. private boolean hasPathTo(int to) {
  100. return distanceTo[to] < Double.POSITIVE_INFINITY;
  101. }
  102. public void printShortestPath(int to) {
  103. if(!hasPathTo(to)){
  104. System.out.println(String.format("%d-%d 不可达",src,to));
  105. }
  106. Stack<WeightedEdge> stack = new Stack<>();
  107. for(Edge edge = lastEdgeTo[to]; edge != null; ){
  108. WeightedEdge we = (WeightedEdge)edge;
  109. stack.push(we);
  110. edge = lastEdgeTo[we.src];
  111. }
  112. System.out.println(String.format("%d-%d的最短路径:",src,to));
  113. while (!stack.isEmpty()) {
  114. WeightedEdge we = stack.pop();
  115. shortestPath.add(we);
  116. System.out.println(String.format("%d-%d %.2f",we.src,we.to,we.weight));
  117. }
  118. }
  119. }

算法的时间复杂度

对所有的边要进行考察,所以有O(E ).

每次考察中,要进行队列的入队或降权操作,队列中最多维护V条记录.最差为O(logV)

所以最差情况下,时间复杂度为O(ElogV).

使用斐波那契堆来代替二叉堆实现的优先队列理论上可以进行有限的优化,因为这种堆的降权(decrease-key)操作的摊还代价为O(1 ),但实际上,它过于长的常量时间并不一定能带来那么美的效率.

单源最短路径-迪杰斯特拉算法(Dijkstra's algorithm)的更多相关文章

  1. 图->最短路径->单源最短路径(迪杰斯特拉算法Dijkstra)

    文字描述 引言:如下图一个交通系统,从A城到B城,有些旅客可能关心途中中转次数最少的路线,有些旅客更关心的是节省交通费用,而对于司机,里程和速度则是更感兴趣的信息.上面这些问题,都可以转化为求图中,两 ...

  2. 迪杰斯特拉算法(Dijkstra) (基础dij+堆优化) BY:优少

    首先来一段百度百科压压惊... 迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法.是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最 ...

  3. 数据结构图之三(最短路径--迪杰斯特拉算法——转载自i=i++

    数据结构图之三(最短路径--迪杰斯特拉算法)   [1]最短路径 最短路径?别乱想哈,其实就是字面意思,一个带边值的图中从某一个顶点到另外一个顶点的最短路径. 官方定义:对于内网图而言,最短路径是指两 ...

  4. 理解最短路径——迪杰斯特拉(dijkstra)算法

    原址地址:http://ibupu.link/?id=29 1.       迪杰斯特拉算法简介 迪杰斯特拉(dijkstra)算法是典型的用来解决最短路径的算法,也是很多教程中的范例,由荷兰计算机科 ...

  5. 迪杰斯特拉算法dijkstra(可打印最短路径)

    #include <iostream> #include <iomanip> #include <string> using namespace std; #def ...

  6. C# 迪杰斯特拉算法 Dijkstra

    什么也不想说,现在直接上封装的方法: using System; using System.Collections.Concurrent; using System.Collections.Gener ...

  7. [从今天开始修炼数据结构]图的最短路径 —— 迪杰斯特拉算法和弗洛伊德算法的详解与Java实现

    在网图和非网图中,最短路径的含义不同.非网图中边上没有权值,所谓的最短路径,其实就是两顶点之间经过的边数最少的路径:而对于网图来说,最短路径,是指两顶点之间经过的边上权值之和最少的路径,我们称路径上第 ...

  8. 最短路径-----迪杰斯特拉算法(C语言版)

    原文:http://blog.csdn.net/mu399/article/details/50903876 转两张思路图非常好:   描述略   图片思路很清晰.  Dijkstra不适用负权值,负 ...

  9. 图的最短路径---迪杰斯特拉(Dijkstra)算法浅析

    什么是最短路径 在网图和非网图中,最短路径的含义是不一样的.对于非网图没有边上的权值,所谓的最短路径,其实就是指两顶点之间经过的边数最少的路径. 对于网图,最短路径就是指两顶点之间经过的边上权值之和最 ...

随机推荐

  1. poj1976

    dp #include <cstdio> #include <cstring> #include <algorithm> using namespace std; ...

  2. python网络编程--线程递归锁RLock

    一:死锁 所谓死锁:是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进 ...

  3. git忽略特殊文件或文件夹

    1.在项目目录中添加“.gitignore”文件,项目目录就是你存放git工程的目录就是有“.git”目录的目录 vi .gitignore 2.在文件中添加如下内容,其中“/runtime/”是忽略 ...

  4. UFLDL 教程学习笔记(三)

    教程地址:http://ufldl.stanford.edu/tutorial/supervised/SoftmaxRegression/ logstic regression是二分类的问题,如果想要 ...

  5. performance 判断页面是以哪种方式进入的

    if (window.performance) { console.info("window.performance is supported"); console.log(per ...

  6. 20165333 学习基础和C语言学习基础

    说实话,我并没有什么技能比90%以上的人更好,非要拿一个出来的话,篮球勉强好一点吧.最初接触篮球是小学的时候跟着哥哥看他打球,哥哥的球技在同龄人中算是好的,每次看他各种突破过人,我都觉得特别潇洒帅气, ...

  7. redux-saga印象

    saga作为redux的中间件,用来处理异步任务. 先收集资料: 贴个文章(2)中的图先: 注意:参考文献(4)是redux-saga作者写的. 参考文章: (1)https://redux-saga ...

  8. 在Kubernetes上使用Traefik

    本节内容: Traefik介绍 部署测试用的两个服务 Role Based Access Control configuration (Kubernetes 1.6+ only) 部署Traefik ...

  9. android拾遗——四大基本组件介绍与生命周期

    Android四大基本组件分别是Activity,Service服务,Content Provider内容提供者,BroadcastReceiver广播接收器. 一:了解四大基本组件 Activity ...

  10. spring boot之使用通用Mapper批量添加数据

    通用Mapper是一款针对mybatis进行扩展的轻量级组件,使用起来非常方便.当调用其针对mysql进行批量添加数据的方法时,发现报错,仔细研究了一番,发现是在使用通用Mapper上出现了问题.下面 ...