单源最短路径-迪杰斯特拉算法(Dijkstra's algorithm)
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)的更多相关文章
- 图->最短路径->单源最短路径(迪杰斯特拉算法Dijkstra)
文字描述 引言:如下图一个交通系统,从A城到B城,有些旅客可能关心途中中转次数最少的路线,有些旅客更关心的是节省交通费用,而对于司机,里程和速度则是更感兴趣的信息.上面这些问题,都可以转化为求图中,两 ...
- 迪杰斯特拉算法(Dijkstra) (基础dij+堆优化) BY:优少
首先来一段百度百科压压惊... 迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法.是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最 ...
- 数据结构图之三(最短路径--迪杰斯特拉算法——转载自i=i++
数据结构图之三(最短路径--迪杰斯特拉算法) [1]最短路径 最短路径?别乱想哈,其实就是字面意思,一个带边值的图中从某一个顶点到另外一个顶点的最短路径. 官方定义:对于内网图而言,最短路径是指两 ...
- 理解最短路径——迪杰斯特拉(dijkstra)算法
原址地址:http://ibupu.link/?id=29 1. 迪杰斯特拉算法简介 迪杰斯特拉(dijkstra)算法是典型的用来解决最短路径的算法,也是很多教程中的范例,由荷兰计算机科 ...
- 迪杰斯特拉算法dijkstra(可打印最短路径)
#include <iostream> #include <iomanip> #include <string> using namespace std; #def ...
- C# 迪杰斯特拉算法 Dijkstra
什么也不想说,现在直接上封装的方法: using System; using System.Collections.Concurrent; using System.Collections.Gener ...
- [从今天开始修炼数据结构]图的最短路径 —— 迪杰斯特拉算法和弗洛伊德算法的详解与Java实现
在网图和非网图中,最短路径的含义不同.非网图中边上没有权值,所谓的最短路径,其实就是两顶点之间经过的边数最少的路径:而对于网图来说,最短路径,是指两顶点之间经过的边上权值之和最少的路径,我们称路径上第 ...
- 最短路径-----迪杰斯特拉算法(C语言版)
原文:http://blog.csdn.net/mu399/article/details/50903876 转两张思路图非常好: 描述略 图片思路很清晰. Dijkstra不适用负权值,负 ...
- 图的最短路径---迪杰斯特拉(Dijkstra)算法浅析
什么是最短路径 在网图和非网图中,最短路径的含义是不一样的.对于非网图没有边上的权值,所谓的最短路径,其实就是指两顶点之间经过的边数最少的路径. 对于网图,最短路径就是指两顶点之间经过的边上权值之和最 ...
随机推荐
- gitHub 迁移到gitlab上
GitHub 迁移到 GitLab 上 第一步在github上生成 token 地址 https://blog.csdn.net/u014175572/article/details/55510825 ...
- lucene-利用内存中索引和多线程提高索引效率
转载地址: http://hi.baidu.com/idoneing/item/bc1cb914521c40603e87ce4d 1.RAMDirectory和FSDirectory对比 RAMDir ...
- less常用样式集,清除浮动、背景自适应、背景渐变、圆角、内外阴影、高度宽度计算。
.clear-float() { content: ''; display: block; clear: both; height:; } //伪元素清除浮动 .after-clear() { &am ...
- javaweb作业一
作业:Http全称叫什么?有什么特点?端口号是多少?超文本传输协议:(1)遵循请求/响应模型(2)http协议是一种无状态协议,请求/响应完成后,连接会断开.这时,服务器无法知道当前访问的用户是否是老 ...
- 每位架构师都应该熟知的 10 个 SOA 设计模式
这 10 个 SOA 设计模式是如此之重要,其应用是如此之广泛,以至于它们都有些显而易见了. 1. 服务无关 服务无关实现对多种业务通用的逻辑.将服务无关的逻辑分离成离散的服务以方便服务的重用和整合. ...
- hdu 1028 整数划分 (母函数)
假如输入44 = 4;4 = 3 + 1;4 = 2 + 2;4 = 2 + 1 + 1;4 = 1 + 1 + 1 + 1;一共5种 假如输入3 用母函数的方法就是写成(1+X+X2+X3)(1+X ...
- progressDialog和子线程模拟显示拷贝进度
package com.example.wang.myapplication; import android.app.ProgressDialog; import android.os.Bundle; ...
- 推荐 远程部署 fabric
自己写的一个例子: from fabric.api import run, env env.hosts = ['nanjing','hefei','haerbin','lanzhou','taiyua ...
- ref:如何将自定义异常的信息显示在jsp页面上
ref:https://blog.csdn.net/tao_ssh/article/details/53486449 在项目中,经常会抛出异常,输出比较友好的信息来提示用户,并指导用户行为.大体思路: ...
- React Native性能优化之可取消的异步操作
前沿 在前端的项目开发中,异步操作是一个不可获取的,从用户的角度来说,异步操作所带来的体验是美妙的,但有时候也会带来一些性能隐患.比如说:有一个异步请求还没有返回结果,但是页面却关闭了,这时由于异步操 ...