Dijkstra 单源最短路径算法 Java实现

  • 算法导入
  • 算法核心
  • 复杂度分析
    • 时间复杂度
    • 空间复杂度
  • 代码实现
  • 参考资料
  • 结尾

算法导入

在图论中,求最短路径有一个经典的算法 Dijkstra算法(银行家算法其实也是这人提出的),就离谱。

如果已经忘了,出门右拐——银行家算法

Dijkstra(/ˈdikstrɑ/或/ˈdɛikstrɑ/)算法由荷兰计算机科学家 E. W. Dijkstra 于 1956 年发现,1959 年公开发表,是一种求解 非负权图 上单源最短路径的算法。

上面一段话有两点需要注意:1. 非负权图。指所有路径的权值都非负。2. 单源。指从一个源点到其他点的最短路径。
那有人想问了,求每对结点之间的最短路径,用啥呢?Floyd算法、Johnson算法

那对于负权图,上面的算法能用吗?能用,你还能用 Bellman-Ford算法

那,我说停停。



好,言归正传。咱们这节先介绍 Dijkstra算法,为后面的几种算法铺个路。正式开始前先告知一些图论中的定义:

  • n ,图上点的数目, m 图上边的数目。
  • s: 为最短路的源点。
  • dis(u) :源点 s 到 u 点的最短路长度。
  • w(u,v) :边(u, v) 的权值。

算法核心

Dijkstra算法的思想为 贪心每次选择最短路长度最小的点,来更新相连边的最短路。从局部最优到全局最优。

初始化:

将结点分为两个集合:已确定最短路点集合 S 和 待确定最短路点集合 T。初始时,所有点都在 T 中。
初始化 dis(s) = 0,其余点的 dis 均为 正无穷。

重复操作:

  1. 从T集合中,选取一个最短路长度最小的结点,移到S集合中
  2. 对刚加入S集合的所有出边,执行松弛操作

直到T集合为空,算法结束。可能有读者不理解松弛操作是啥,公式解答如下:
对于边 (u,v) 松弛操作为: dis(v) = min(dis(v) , dis(u) + w(u,v) )


你在的城市只能坐绿皮火车去北京,哎呀老远了,要1天才能到达。突然你发现去省会只要2小时,省会坐飞机去北京只要5小时,合计7个小时,这不更香了?那肯定赶紧更新这个最短路径。

复杂度分析

判断一个算法的优劣,我们需要对算法进行复杂度分析。复杂度分析从两个维度来评判,一个是时间,一个是空间。因此,这部分我们将对Dijkstra算法进行复杂度分析。

时间复杂度

算法核心操作:

  1. 从T集合中,选取一个最短路长度最小的结点,移到S集合中
  2. 对刚加入S集合的所有出边,执行松弛操作

对于2来说,我们只需要 O(1) 的时间复杂度,重点在于如何维护最短路长度最小的结点。

  • 暴力:枚举才是永远滴神。每次操作2结束后。直接在T集合中暴力寻找最短的点,需要O(n),进行 n 次循环。总时间复杂度为 O(n^2);2 操作对于边做处理,时间复杂度为O(m)。全过程的时间复杂度为 O(n^2 + m) = O(n^2)
  • 二叉堆:每成功松弛一条边(u, v),就将 v 插入二叉堆中如果v已经在二叉堆中,直接修改相应的元素的权值即可,1操作直接取堆顶结点。共计O(m)次二叉堆上的插入(修改操作),O(n) 次 删除堆顶操作,而插入和删除元素的时间复杂度为O(logn) ,时间复杂度为 O((n + m) logn)
  • 优先队列:和二叉树类似,但使用优先队列时,如果同一个点的最短路被更新多次,因为先前更新时插入的元素不能被删除,也不能被修改,只能留在优先队列中,故优先队列内的元素个数是O(m) 的,时间复杂度为 O(mlogm)
  • Fibonacci堆、线段树方式,这里咱不考虑。有兴趣同学可以自行研究。

在稀疏图中,m = O(n),使用二叉堆实现的 Dijkstra 算法较 Bellman-Ford 算法具有较大的效率优势;而在稠密图中,这时候使用暴力做法较二叉堆实现更优。

空间复杂度

空间复杂度的主要消耗在于图的存储方式。

  • 邻接矩阵:空间复杂度为 O(n^2),消耗二维数组的空间。
  • 邻接表:空间复杂度为 O(m)。因为存储的是边信息。

注:该部分参考 OI Wiki,详见:oi wiki

代码实现

由于图不同的存储方式,复杂度也不同,这里只提供较为普遍使用的两种存图方式。

针对稀疏图 m ≈ n,邻接表存图,优先队列维护最短路长度最小的结点。

import java.util.*;/*** @author LKQ* @date 2022/4/21 16:20* @description Dijkstra算法:一种求解 非负权图上 单源最短路径算法,流程有两步:* 将结点分成两个集合:已确定最短路长度的点集(记为 S 集合)的和未确定最短路长度的点集(记为 T 集合)。一开始所有的点都属于 T 集合。* 定义:n 为 图上点的数目,m 为 图上边的数目* s 为最短路的源点* D(u) 为 s点到 u点的实际最短路长度,dis(u)为 s -> u 点的 估计最短路长度,* w(u,v)为 (u, v)这一条边的边权值。* 初始化 dis(s) = 0,其他点的 dis 均为 +∞。* <p>* 然后重复这些操作:* <p>* 1. 从 T 集合中,选取一个最短路长度最小的结点,移到 S集合中。* 2. 对那些刚刚被加入 S 集合的结点的所有出边执行松弛操作。* 直到 T 集合为空,算法结束。* <p>* 针对稠密网,当 边数量 接近点数量的平方时,采用邻接矩阵存图,枚举算法进行更好* 针对稀疏网,当 边数量接近点的数量时,采用邻接表存图,优先队列实现Dijkstra更好*/
public class Solution {/*** 邻接表存储图*/List<int[]>[] graph;/*** 源点到其他点的最短距离,dis[s] = 0, 其他为 +∞*/int[] dis;/*** 结点 0 - n-1 是否访问*/boolean[] vis;/*** 正无穷,除2的意义在于 距离相加时不会溢出int*/public static final int INF = Integer.MAX_VALUE / 2;/*** 算法** @param n    n个结点* @param edge 有向边 + 权值,如[1, 2, 5]表示 结点 1->2 的边权值为 5* @param s    源点 0 <= s < n*/public void dijkstra(int n, int[][] edge, int s) {// 1. 抽象化。根据edge信息建图。// 注意:有时候edge并不会直接给出,比如一些题目给出的是字符串表示的结点,那么需要使用 Map<String, Integer> 来 给字符串编号,再抽象化graph = new List[n];for (int i = 0; i < n; i++) {graph[i] = new ArrayList<>();}for (int[] e : edge) {// w(e[0], e[1]) = e[2] graph[e[0]].add(new int[]{e[1], e[2]});}// 2. 初始化源点到其他点的最短距离dis = new int[n];Arrays.fill(dis, INF);// 源点到自身的距离为0dis[s] = 0;// 3. 初始化访问标志,默认为falsevis = new boolean[n];// 4. 初始化优先队列, 根据权值升序排序PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]);pq.add(new int[]{s, 0});// 5. dijkstrawhile (!pq.isEmpty()) {// 弹出最短路长度最小的点 和 其权值int[] temp = pq.poll();int u = temp[0];// 访问过,跳过if (vis[u]) {continue;}vis[u] = true;// 遍历所有 u 能够到达的点,刚开始为 u = sfor (int[] q: graph[u]) {// 下一个点 v, 即其边权值 wint v = q[0], w = q[1];if (dis[v] > dis[u] + w) {// s->v 的距离 > s->u 的距离 + u->v 的距离,更新最短距离,注意时 s-> 其他点 距离为 +∞dis[v] = dis[u] + w;// 加入优先队列,s->v 的距离 dis[v]pq.add(new int[]{v, dis[v]});}}}}
}

针对稠密图m ≈ n^2,采用邻接矩阵存储

import java.util.*;/*** @author LKQ* @date 2022/4/21 20:20* @description 当边的数量接近点的数量,用邻接矩阵存储图,*/
public class Solution2 {/*** 邻接矩阵存储图*/int[][] graph;/*** 判断是否访问过*/boolean[] vis;/*** 源点到其他点的最短距离,dis[s] = 0, 其他为 +∞*/int[] dis;/*** 无穷大*/int INF = Integer.MAX_VALUE / 2;/*** @param n n个结点,编号 0 .. n-1* @param s 源点* @param edges 边信息*/public void dijkstra(int n, int s, int[][] edges) {// 1. 初始化邻接矩阵graph = new int[n][n];for (int i = 0; i < n; i++) {Arrays.fill(graph[i], INF);// 点到自身的权值为0graph[i][i] = 0;}for (int[] e : edges) {graph[e[0]][e[1]] = e[2];}// 2. 初始化源点到其他点的距离dis = new int[n];Arrays.fill(dis, INF);// 源点到自身的距离为0dis[s] = 0;// 3. 初始化访问标志vis = new boolean[n];// 4. 迭代n次for (int i = 0; i < n; i++) {// 每次找到[最短距离最小] 且 [没有更新] 点 tint u = 0, min = INF;for (int j = 0; j < n; j++) {if (!vis[j] && dis[j] < min) {u = j;min = dis[j];}}vis[u] = true;// 用点u的[最小距离]更新其他点for (int j = 0; j < n; j++) {if (dis[u] + graph[u][j] < dis[j]) {dis[j] = dis[u] + graph[u][j];}}}}
}

参考资料

OI Wiki
图灵程序设计丛书 算法 第4版

结尾

如果有天兜兜转转再一次相遇 我一定会不顾一切紧紧抱着你
许嵩 《庞贝》

Dijkstra 单源最短路径算法 Java实现相关推荐

  1. C++实现dijkstra单源最短路径算法-邻接表+优先队列

    dijkstra单源最短路径算法不允许边权值为负,适用的图范围可以很大. 代码如下: #include <iostream> #include <queue> #include ...

  2. Dijkstra单源最短路径算法

    这里写目录标题 一.算法原理 二.MATLAB实现 三.参考文献 一.算法原理 迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法.是从一个顶 ...

  3. C++实现有向图最短路径-Dijkstra单源最短路径算法

    #include <iostream> using namespace std; #define INFINE 99999999//假装我是无穷大 const int N = 1010; ...

  4. 单源路径分支界限java_java单源最短路径算法

    . .. .. . 单源最短路径的 Dijkstra 算法: 问题描述: 给定一... 并 应用贪心法求解单源最短路径问题.环境要求对于环境没有特别要求.对于算法实现,可以自由选择 C, C++, J ...

  5. 图论-单源最短路径算法(拓扑,Dijkstra,Floyd,SPFA)

    前言 单源最短路径是学习图论算法的入门级台阶,但刚开始看的时候就蒙了,什么有环没环,有负权没负权,下面就来总结一下求单源最短路径的所有算法以及其适用的情况. 单源最短路径 设定图中一个点为源点,求其他 ...

  6. Johnson 全源最短路径算法 Java实现

    Johnson 全源最短路径算法 Java实现 算法导入 算法核心 复杂度分析 时间复杂度 空间复杂度 代码实现 参考资料 End 算法导入 在之前的文章中,我们讲述了: 经典入门的Dijkstra算 ...

  7. Bellman-Ford 单源最短路径算法

    Bellman-Ford 单源最短路径算法 Bellman-Ford 算法是一种用于计算带权有向图中单源最短路径(SSSP:Single-Source Shortest Path)的算法.该算法由 R ...

  8. 四种不同单源最短路径算法性能比较

    四种不同单源最短路径算法性能比较   一.最短路径问题描述 单源最短路径描述:给定带权有向图G=(V,E),其中每条边的权是非负实数.另外,还给定V中的一个顶点,称之为源.现在要计算从源到其他各顶点的 ...

  9. 应用单源最短路径算法解决套利交易问题

    目录 导言 基本定义与延伸 基本概念与定义 概念延伸 最短路径算法 Bellman-Ford算法 问题简述 问题分析 问题求解 第一题 第二题 总结 导言 最短路径问题是图论领域中的核心问题之一,也是 ...

最新文章

  1. 16 BasicHashTable基本哈希表类(三)——Live555源码阅读(一)基本组件类
  2. 《微信公众平台开发:从零基础到ThinkPHP5高性能框架实践》——3.3 微信开发者中心...
  3. html提供的常用的页面交换元素有哪些,一些常用网页制作代码收集汇总
  4. Cocos2d-x VS. OGEngine,联盟与部落的战争
  5. mysql 逻辑备份 物理备份_数据库的逻辑备份和物理备份--非RMAN
  6. JavaScript中九九乘法表制作
  7. 计算机网络 --- 传输层
  8. Neutron系列 : Neutron OVS OpenFlow 流表 和 L2 Population(8)
  9. 判断字符串解析是JsonObject或者JsonArray
  10. python 中__init__ 与 __call__ 的区别
  11. python np.linspace
  12. cdr 表格自动填充文字_操作基础知识Word文字编辑
  13. 逻辑回归-逐步回归(stepwise regression)的一些思考
  14. 移动iptv安装三方软件
  15. 洛谷----P3717 [AHOI2017初中组]cover
  16. Python实现数字转变为Excel的列
  17. 百度UNIT 机器人多轮对话技能创建以及API调用
  18. SQLite WAL 机制探索
  19. 新书上市第13天,在亚马逊Kindle电子书人工智能榜第三,与《未来简史》和李开复《人工智能》同榜
  20. 交换机、路由器、服务器组网

热门文章

  1. ESP32 ESP-IDF使用TF(SD)卡
  2. 符号位处理方式 c语言,C语言位域解析符号位扩展规则
  3. 第三方物流运作系统及其战略
  4. 第三方物流学习(三)
  5. android克隆漏洞分析,Android支付宝克隆漏洞
  6. 栈和队列相互实现 (用队列实现栈/用栈实现队列) 超详细~
  7. flinksql获取系统当前时间搓_sql 时间戳
  8. android opencv卡顿,Opencv读取摄像头卡顿
  9. 【论文笔记】SSCDNet:弱监督语义场景变化检测模型
  10. Git是什么,如何使用