Dijkstra’s algorithm

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

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

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

算法描述

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

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

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

    /**
* 对边进行松弛
*
* 对一条有向边(v,w,weight)进行考察:
* 如果当前已知的到w的距离distance_to(w) > distance_to(v) + weight,
* 说明可以改善到w的距离.从而更新这个距离:
* distance_to(w) = distance_to(v) + weight;
* 同时选择这条边为到w的目前已知的最短边
* last_edge_to(w) = (v,w,weight)
*
* @param edge 有向带权边
*/
private void relaxEdge(WeightedEdge edge) {
IndexPriorityQueue<Double> q = indexCrossingEdges;
int src = edge.src;
int to =edge.to;
if(distanceTo[to] > distanceTo[src] + edge.weight) {
distanceTo[to] = distanceTo[src] + edge.weight;
lastEdgeTo[to] = edge;
if(q.contains(to))
q.decreaseKey(to,distanceTo[to]);
else
q.offer(to,distanceTo[to]);
}
}

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

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

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

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

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

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

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

实现代码

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

/**
* Created by 浩然 on 4/21/15.
*/
public class Dijkstra {
/**
* 当前已知的最短距离,索引为顶点的编号
* 比如distanceTo[v]表示当前到顶点v的最短距离
*/
protected double[] distanceTo;
/**
* 当前已知的最短边,索引为顶点的编号
* 比如lastEdgeTo[v]表示当前到顶点v的最短边
*/
protected WeightedEdge[] lastEdgeTo;
/**
* 索引式优先队列,维护到顶点的最短距离
*/
protected IndexPriorityQueue<Double> indexCrossingEdges;
/**
* 有向带权图
*/
private WeightedDirectedGraph g;
private LinkedList<WeightedEdge> shortestPath; /**
* 单源
*/
private int src; public Dijkstra(WeightedDirectedGraph g){
this.g = g;
} public void performSP(int src) {
this.src = src;
validateEdges();
resetMemo();
IndexPriorityQueue q = indexCrossingEdges; //从源点开始
distanceTo[src] = 0;
q.offer(src,distanceTo[src]); while (!q.isEmpty()) {
src = q.poll();
for(Edge e:g.vertices()[src].Adj) {
relaxEdge((WeightedEdge)e);
}
}
} /**
* 对边进行松弛
*
* 对一条有向边(v,w,weight)进行考察:
* 如果当前已知的到w的距离distance_to(w) > distance_to(v) + weight,
* 说明可以改善到w的距离.从而更新这个距离:
* distance_to(w) = distance_to(v) + weight;
* 同时选择这条边为到w的目前已知的最短边
* last_edge_to(w) = (v,w,weight)
*
* @param edge 有向带权边
*/
private void relaxEdge(WeightedEdge edge) {
IndexPriorityQueue<Double> q = indexCrossingEdges;
int src = edge.src;
int to =edge.to;
if(distanceTo[to] > distanceTo[src] + edge.weight) {
distanceTo[to] = distanceTo[src] + edge.weight;
lastEdgeTo[to] = edge;
if(q.contains(to))
q.decreaseKey(to,distanceTo[to]);
else
q.offer(to,distanceTo[to]);
}
} private void validateEdges() {
for(Vertex v:g.vertices()) {
for(Edge e:v.Adj) {
if(((WeightedEdge) e).weight < 0) {
throw new IllegalArgumentException("边的权值不能为负!");
}
}
}
} private void resetMemo() {
int vertexCount = g.vertexCount();
//重置sp
shortestPath = new LinkedList<>();
//重置所有已知最短路径
lastEdgeTo = new WeightedEdge[vertexCount];
//重置所有距离
distanceTo = new double[vertexCount];
for(int i = 0; i < distanceTo.length; i++) {
distanceTo[i] = Double.POSITIVE_INFINITY;
}
//重置优先队列
indexCrossingEdges = new IndexPriorityQueue<>();
} /**
* 从源点到目标点是否存在一条路径
* @param to 目标点
* @return 存在则返回真,否则返回假
*/
private boolean hasPathTo(int to) {
return distanceTo[to] < Double.POSITIVE_INFINITY;
} public void printShortestPath(int to) {
if(!hasPathTo(to)){
System.out.println(String.format("%d-%d 不可达",src,to));
} Stack<WeightedEdge> stack = new Stack<>();
for(Edge edge = lastEdgeTo[to]; edge != null; ){
WeightedEdge we = (WeightedEdge)edge;
stack.push(we);
edge = lastEdgeTo[we.src];
}
System.out.println(String.format("%d-%d的最短路径:",src,to)); while (!stack.isEmpty()) {
WeightedEdge we = stack.pop();
shortestPath.add(we);
System.out.println(String.format("%d-%d %.2f",we.src,we.to,we.weight));
}
}
}

算法的时间复杂度

对所有的边要进行考察,所以有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. C++中关于位域的概念

    原文来自于http://topic.csdn.net/t/20060801/11/4918904.html中的回复 位域 有些信息在存储时,并不需要占用一个完整的字节,   而只需占几个或一个二进制位 ...

  2. 24 The Go image package go图片包:图片包的基本原理

    The Go image package  go图片包:图片包的基本原理 21 September 2011 Introduction The image and image/color packag ...

  3. ggplot2使用初探

    ggplot2已经成为了R语言中数据可视化的同义词, 这是一个强大的工具, 可以帮助我们制作优良的图表, 创造出令人吃惊的图片, 下面我们一起学习(本博文参考了知乎问题如何使用 ggplot2中黄宝臣 ...

  4. 浅谈js设计模式之单例模式

    单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点. 单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池.全局缓存.浏览器中的 window 对象等.在 JavaS ...

  5. 安卓ios各版本及分辨率占比

    Google Play 安装统计数据 只有安卓的 https://developer.android.com/about/dashboards/index.html?hl=zh-cn 腾讯移动分析 安 ...

  6. 洛谷P3621风铃

    传送门啦 分析: 这个题看起来像是个树形dp,嗯,就是看起来像. 所以我们就按树形dp的思路去分析就好了,这个题是一个树形dp的变形题. 和以前建树是一样的,我们用邻接表来进行储存.利用邻接表的特性, ...

  7. 20165333 2017-2018-2《Java程序设计》课程总结

    一.每周作业链接汇总 1.预备作业一:我期望的师生关系 简要内容: 印象深刻的老师 我期望的师生关系 关于JAVA学习 2.预备作业二:学习基础和C语言学习基础 简要内容: 技能学习 C语言学习 关于 ...

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

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

  9. 2017-2018 ACM-ICPC Pacific Northwest Regional Contest (Div. 1) M - Unsatisfying 2-Sat

    题目大意:给你 m 个式子, 问你最少再添加多少式子能使没有任何一种赋值方式使全部的式子为真. 并且在你给的式子中不能有非. 思路:根据题目给的m个式子可以建出2-Sat的图, 现在问你最少加多少个边 ...

  10. hdoj1203 I NEED A OFFER!(DP,01背包)

    题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=1203 思路 求最少能收到一份offer的最大概率,可以先求对立面:一份offer也收不到的最小概率,然 ...