今天是算法数据结构专题的第34篇文章,我们来继续聊聊最短路算法。

在上一篇文章当中我们讲解了bellman-ford算法和spfa算法,其中spfa算法是我个人比较常用的算法,比赛当中几乎没有用过其他的最短路算法。但是spfa也是有缺点的,我们之前说过它的复杂度是

,这里的E是边的数量。但有的时候边的数量很多,E最多能够达到
,这会导致超时,所以我们会更换其他的算法。这里说的其他的算法就是Dijkstra。

算法思想

在上一篇文章当中我们曾经说过Bellman-ford算法本质上其实是动态规划算法,我们的状态就是每个点的最短距离,策略就是可行的边,由于一共最多要松弛V-1次,所以整体的算法复杂度很高。当我们用队列维护可以松弛的点之后,就将复杂度降到了

,也就是spfa算法。

Dijkstra算法和Bellman-ford算法虽然都是最短路算法,但是核心的逻辑并不相同。Dijkstra算法的底层逻辑是贪心,也可以理解成贪心算法在图论当中的使用。

其实Dijstra算法和Bellman-ford算法类似,也是一个松弛的过程。即一开始的时候除了源点s之外,其他的点的距离都设置成无穷大,我们需要遍历这张图对这些距离进行松弛。所谓的松弛也就是要将这些距离变小。假设我们已经求到了两个点u和v的距离,我们用dis[u]表示u到s的距离,dis[v]表示v的距离。

假设我们有dis[u] < dis[v],也就是说u离s更近,那么我们接下来要用一个新的点去搜索松弛的可能,u和v哪一个更有可能获得更好的结果呢?当然是u,所以我们选择u去进行新的松弛,这也就是贪心算法的体现。如果这一层理解了,算法的整个原理也就差不多了。

我们来整理一下思路来看下完整的算法流程:

  1. 我们用一个数组dis记录源点s到其他点的最短距离,起始时dis[s] = 0,其他值设为无穷大
  2. 我们从未访问过的点当中选择距离最小的点u,将它标记为已访问
  3. 遍历u所有可以连通的点v,如果dis[v] < dis[u] + l[u] [v],那么更新dis[v]
  4. 重复上述2,3两个步骤,直到所有可以访问的点都已经访问过

怎么样,其实核心步骤只有两步,应该很好理解吧?我找到了一张不错的动图,大家可以根据上面的流程对照一下动图加深一下理解。

我们根据原理不难写出代码:

INF = sys.maxsize
edges = [[]] # 邻接表存储边
dis = [] # 记录s到其他点的距离
visited = {} # 记录访问过的点while True:mini = INFu = 0flag = False# 遍历所有未访问过点当中距离最小的for i in range(V):if i not in visited and dis[i] < mini:mini, u = dis[i], iflag = True# 如果没有未访问的点,则退出if not flag:breakvisited[u] = Truefor v, l in edges[u]:dis[v] = min(dis[v], dis[u] + l)

虽然我们已经知道算法没有反例了,但是还是可以思考一下。主要的点在于我们每次都选择未访问的点进行松弛,有没有可能我们松弛了一个已经访问的点,由于它已经被松弛过了,导致后面没法拿来松弛其他的点呢?

其实是不可能的,因为我们每次选择的都是距离最小的未访问过的点。假设当前的点是u,我们找到了一个已经访问过的点v,是不可能存在dis[u] + l < dis[v]的,因为dis[v]必然要小于dis[u],v才有可能先于u访问。但是这有一个前提,就是每条边的长度不能是负数。

算法优化

和Bellman-ford算法一样,Dijkstra算法最大的问题同样是复杂度。我们每次选择一个点进行松弛,选择的时候需要遍历一遍所有的点,显然这是非常耗时的。复杂度应该是

,这里的E是边的数量,Dijkstra中每个点只会松弛一次,也就意味着每条边最多遍历一次。

我们观察一下会发现,外面这层循环也就算了,里面这层循环很没有必要,我们只是找了一个最值而已。完全可以使用数据结构来代替循环查询,维护最值的场景我们也已经非常熟悉了,当然是使用优先队列。

使用优先队列之后这段代码会变得非常简单,同样也不超过十行,为了方便同学们调试,我把连带优先队列实现的代码一起贴上来。

import heapq
import sys# 优先队列
class PriorityQueue:def __init__(self):self._queue = []self._index = 0def push(self, item, priority):# 传入两个参数,一个是存放元素的数组,另一个是要存储的元素,这里是一个元组。# 由于heap内部默认由小到大排,所以对priority取负数heapq.heappush(self._queue, (-priority, self._index, item))self._index += 1def pop(self):return heapq.heappop(self._queue)[-1]def empty(self):return len(self._queue) == 0que = PriorityQueue()INF = sys.maxsize
edges = [[], [[2, 7], [3, 9], [6, 14]], [[1, 7], [3, 10], [4, 15]], [[1, 9], [2, 10], [6, 2], [4, 11]], [[3, 11], [5, 6]], [[4, 6], [6, 9]], [[3, 2], [5, 9]]] # 邻接表存储边
dis = [sys.maxsize for _ in range(8)] # 记录s到其他点的距离
s = 1
que.push(s, 0)
dis[s] = 0
visited = {}while not que.empty():u, d = que.pop()if d != dis[u]:continuefor v, l in edges[u]:if dis[u] + l < dis[v]:dis[v] = dis[u] + lque.push(v, dis[v])print(dis)

这里用visited来判断是否之前访问过的主要目的是为了防止负环的产生,这样程序会陷入死循环,如果确定程序不存在负边的话,其实可以没必要判断。因为先出队列的一定更优,不会存在之后还被更新的情况。如果想不明白这点加上判断也没有关系。

我们最后分析一下复杂度,每个点最多用来松弛其他点一次,加上优先队列的调整耗时,整体的复杂度是

,比之前
的复杂度要提速了很多,

非常适合边很多,点相对较少的图。有时候spfa卡时间了,我们会选择Dijkstra。

今天的文章到这里就结束了,如果喜欢本文的话,请来一波素质三连,给我一点支持吧(关注、转发、点赞)。

算法数据结构 | 10行实现最短路算法——Dijkstra​mp.weixin.qq.com

spfa算法_10行实现最短路算法——Dijkstra相关推荐

  1. 算法小讲堂之最短路算法(Floyd+bellman+SPFA+Dijkstra)

    前言 如果你对图论相关知识一点也没有,那么建议您先去了解这些知识:https://acmer.blog.csdn.net/article/details/122310835,然后就可以快乐的学习最短路 ...

  2. 最短路算法总结(入门版)

    最近花了大约一个月左右的时间集中刷了一些图论的题目.虽然收获了许多但是还是付出了很多作业没有做的代价0.0.在这里先把自己所做的关于最短路的基础算法来做一个总结,至少把学到的东西记录下来. 先说明一下 ...

  3. dijkstra算法matlab程序_编程习题课 | 用最短路算法为你的小地图导航

    简介:路网拓扑的正确导入方式,运筹学算法的完整实战案例,最详细的代码讲解与分享. 引言:在研究路径选择和流量分配等交通问题时,常常会用到最短路算法.用最短路算法解决交通问题存在两个难点:一.算法的选择 ...

  4. zuc算法代码详解_最短路算法-dijkstra代码与案例详解

    引言 在研究路径选择和流量分配等交通问题时,常常会用到最短路算法.用最短路算法解决交通问题存在两个难点: 一.算法的选择和程序的编写.最短路算法有很多种改进算法和启发式算法,这些算法的效率不同,适用的 ...

  5. 最短路算法 :Bellman-ford算法 Dijkstra算法 floyd算法 SPFA算法 详解

     本文链接   :http://www.cnblogs.com/Yan-C/p/3916281.html . 在本文中因为邻接表在比赛中不如前向星好写,而且前向星效率并不低所以,本文的代码 存图只 ...

  6. 详解最短路算法模板(dijkstra+floyd+spfa)

    1.Floyd_Warshall算法 核心思路:d[i][j] = min{d[i][j], d[i][k] + d[k][j]} 从i到j有两种路径,经过k点或是不经过k点,所以我们枚举k即可求所有 ...

  7. 最短路算法详解(Dijkstra/SPFA/Floyd)

    转自:http://blog.csdn.net/murmured/article/details/19281031 一.Dijkstra Dijkstra单源最短路算法,即计算从起点出发到每个点的最短 ...

  8. 单源(多源)最短路算法Dijkstra、Bellman-Ford、SPFA

    最短路算法 单源最短路:即一个点到任意点的最短路径 多源最短路:即任意一点到任意一点的最短路径 Dijkstra算法: 这个算法是通过点去更新最短路,每次找离源点最近的一个顶点,然后以该顶点为中心进行 ...

  9. 【最短路算法】dijkstra,SPFA和folyd

    在了解本知识点之前,首先要了解图的建立,也就是邻接表和邻接矩阵. 邻接矩阵适用于稠密图 邻接表适用于稀疏图 目录 单源最短路问题 dijkstra算法 SPFA算法 folyd算法 最小生成树 拓扑排 ...

最新文章

  1. 深入浅出统计学(十二)置信区间
  2. 每天被远程办公支配的恐惧,你怕了吗?
  3. kafka基础架构详解
  4. 南阳医专计算机专业代码,南阳医学高等专科专业招生代码
  5. 数据结构与算法之KMP算法
  6. 【6】C++语法与数据结构之STL_list学生管理系统_链表外排序_函数指针
  7. 写cookies注意事项
  8. [react] react怎么提高列表渲染的性能?
  9. 【基础】位与运算与取余
  10. Redis学习手册(主从复制)
  11. R语言各个包里面的数据集
  12. 从计算、建模到回测:因子挖掘的最佳实践
  13. django下载xlsx的方法
  14. 局域网搭建git服务器
  15. 宽搜入门代码模板详解 HDOJ1253
  16. Extraneous non-emits event listeners (onJump) were passed to component but could not be automaticall
  17. python 基于xgboost预测波士顿房价
  18. 目标检测YOLOv3
  19. laravel 使用SSH 隧道连接到远程数据库
  20. 4月10日第七次CCF

热门文章

  1. java 匿名接口实现_Java通过接口实现匿名类的实例代码
  2. PAT乙级(1021 个位数统计)
  3. 云和恩墨张皖川:产品能力提升是推动国产替代进程的关键因素
  4. 国产数据库丨国产数据库发展十策(一):开发一个数据库到底需要多少人?...
  5. 【警惕】大量未修复WebLogic WSAT组件RCE漏洞的主机被挖矿程序攻击
  6. 云小课|细数那些VMware虚拟机的恢复招式
  7. 通用时区:你应该知道的数据库时区知识
  8. Scrum指南这么改,我看要完蛋!
  9. 无惧海量并发,运维准点下班全靠它
  10. 一文读懂GaussDB(for Mongo)的计算存储分离架构