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. VM虚拟机和主机互传文件,使用xshell连接Ubuntu

    安装虚拟机后,有时需要在window和Ubuntu互传文件,安装VMwave tooles比较麻烦,干脆直接用xshell连接Ubuntu即可 1,已经安装Ubuntu和xshell 2,在Ubunt ...

  2. NVME SSD vs SATA SSD(转)

    NVMe是个啥?未来SSD主流标准早知 关注固态硬盘的朋友应该对于这个词汇并不陌生,特别是今年NVMe也频繁出现在各大媒体文章中,随着高端SSD市场逐渐从SATA专项PCI-E时,以前的AHCI标准已 ...

  3. scala tuple中的syntactic sugar

    List[Tuple2[String, Int]] // Base List[(String, Int)] // Syntactic sugar List[Tuple3[String, Float, ...

  4. windows 下的一些常用命令提示符

    windows下dos命令窗口输入 netstat -ano即可查看端口使用情况, 如果要查看指定端口是否被占用 使用命令netstat -ano|findstr 端口号, 例如要查看8080端口号是 ...

  5. dede导航栏目调用

    1.基础调用 {dede:channel row='5' type ='top' } <li><a href="[field:typelink/]">[fi ...

  6. centos7.2安装mysql5.7

    1.安装前工作 在安装前需要确定现在这个系统有没有 mysql,如果有那么必须卸载(在 centos7 自带的是 mariaDb 数据库,所以第一步是卸载数据库). 卸载系统自带的Mariadb: 查 ...

  7. CCF CSP 201503-4 网络延时

    CCF计算机职业资格认证考试题解系列文章为meelo原创,请务必以链接形式注明本文地址 CCF CSP 201503-4 网络延时 问题描述 给定一个公司的网络,由n台交换机和m台终端电脑组成,交换机 ...

  8. Storm(一)Storm的简介与相关概念

    一.Storm的简介 官网地址:http://storm.apache.org/ Storm是一个免费开源.分布式.高容错的实时计算系统.Storm令持续不断的流计算变得容易,弥补了Hadoop批处理 ...

  9. 站点的安全防范都是后端的职责?非也,Web前端安全同样不可忽视

    前言 随着网络的快速普及,网络安全问题的受害者不再只是政府.企业等集体,每一个接触网络的普通人都有可能成为网络攻击的受害者.随着网络的普及,黑客进行网络攻击的手段越来也多,越来越复杂.以网站的攻击为例 ...

  10. C++11线程池的实现

    什么是线程池 处理大量并发任务,一个请求一个线程来处理请求任务,大量的线程创建和销毁将过多的消耗系统资源,还增加了线程上下文切换开销. 线程池通过在系统中预先创建一定数量的线程,当任务请求到来时从线程 ...