今天是算法数据结构专题的第33篇文章,我们一起来聊聊最短路问题。

最短路问题也属于图论算法之一,解决的是在一张有向图当中点与点之间的最短距离问题。最短路算法有很多,比较常用的有bellman-ford、dijkstra、floyd、spfa等等。这些算法当中主要可以分成两个分支,其中一个是bellman-ford及其衍生出来的spfa,另外一个分支是dijkstra以及其优化版本。floyd复杂度比较高,一般不太常用。

我们今天的文章介绍的是bellman-ford以及它的衍生版本spfa算法。

图的存储

我们要对一张有向图计算最短路,那么我们首先要做的就是将一张图存储下来。关于图的存储的数据结构,常用的方法有很多种。最简单的是邻接矩阵,所谓的邻接矩阵就是用一个二维矩阵存储每两个点之间的距离。如果两个点之间没有边相连,那么设为无穷大。

可以参考一下下图。

这种方法的好处是非常直观,实现也很简单,但是缺点也很明显。这个二维矩阵所消耗的空间复杂度是,这里的V指的是顶点的数量。当顶点的数量稍稍大一些之后,带来的开销是非常庞大的。一般情况下我们的图的边的密集程度是不高的,也就是说大量点和点之间没有边相连,我们浪费了很多空间。

针对邻接矩阵浪费空间的问题,后来又提出了一种新的数据结构就是邻接表

所谓的邻接表也就是说我们把顶点一字排开存入数组当中,每个顶点对应一条链表。这条链表当中存储了这个点可以到达的其他点的信息。比如下图当中V1点可以连通V2和V3,V1在数组当中的下标为0,所以下标为0的元素是一个存储了V2和V3下标的链表,也就是图中的1和2。

邻接表的好处是可以最大化利用空间,有多少条边存储多少信息。但是也有缺点,除了实现稍稍复杂一点之外,另外一个明显的缺点就是我们没办法直接判断两点之间是否有边存在,必须要遍历链表才可以。

除了邻接矩阵和邻接表之外,还有一些其他的数据结构可以完成图的存储。比如前向星、边集数组、链式前向星等等。这些数据结构并没有比邻接表有质的突破,对于非算法竞赛同学来说,能够熟练用好邻接表也就足够了。

Bellman-Ford算法

刚才上面描述当中提到的算法除了floyd算法是计算的所有点对之间的最短距离之外,其他算法解决的都是单源点最短路的问题。所谓的单源点可以简单理解成单个的出发点,也就是说我们求的是从图上的一个点出发去往其他每个点的最短距离。既然如此,我们的出发点以及到达点都是确定的,不确定的只是它们之间的距离而已。

为什么我们会将bellman-ford算法和dijkstra算法区分开呢?因为两者的底层逻辑是不同的,bellman-ford算法的底层逻辑是动态规划, 而dijkstra算法的底层逻辑是贪心。

bellman-ford算法的得名也和人有关,我们之前在介绍KMP算法的时候曾经说过。由于英文表意能力不强,所以很多算法和公式都是以人名来取名。bellman-ford是Richard Bellman 和 Lester Ford 分别发表的,实际上还有一个叫Edward F. Moore也发表了这个算法,所以有的地方会称为是Bellman-Ford-Moore 算法。

算法的原理非常简单,利用了动态规划的思想来维护源点出发到各个点的最短距离

它的核心思维是松弛,所谓的松弛可以理解成找到了更短的路径对原路径进行更新。对于一个有V个节点的有向图进行V-1轮松弛,从而找到源点到所有点的最短距离。

初始的时候我们会用一个数组记录源点到其他所有点的距离,对于与源点直接相连的点来说,这个距离就是它和源点的距离否则则是无穷大。对于第一轮松弛来说,我们寻找的是源点出发经过一个点到达其他点的最短距离。我们用s代表源点,我们寻找的就是s经过点a到达点b,使得距离小于s直接到b的距离。

第二轮松弛就是寻找的s经过两个点到达第三个点的最短距离,同理,对于一个有V个点的图来说,两个点之间最多经过V-1个点,所以我们需要V-1轮松弛操作。

它的伪代码非常简单,我们直接来看:

for (var i = 0; i < n - 1; i++) {
    for (var j = 0; j < m; j++) {//对m条边进行循环
      var edge = edges[j];
      // 松弛操作
      if (distance[edge.to] > distance[edge.from] + edge.weight ){ 
        distance[edge.to] = distance[edge.from] + edge.weight;
      }
    }
}

Bellman-ford的算法很好理解,实现也不难,但是它有一个缺点就是复杂度很高。我们前面说了一共需要V-1轮松弛,每一轮松弛我们都需要遍历E条边,所以整体的复杂度是,E指的是边的数量。想想看,假设对于一个有1w个顶点,10w条边的图来说,这个算法是显然无法得出结果的。

所以为了提高算法的可用性,我们必须对这个算法进行优化。我们来分析一下复杂度巨大的原因,主要在两个地方,一个地方是我们松弛了V-1次,另外一个地方是我们枚举了所有的边。松弛V-1次是不可避免的,因为可能存在极端的情况需要V-1次松弛才可以达成。但我们每次都枚举了所有的边感觉有点浪费,因为其中大部分的边是不可能达成新的松弛的。那有没有办法我们筛选出来可能构成新的松弛的边呢?

针对这个问题的思考和优化引出了新的算法——spfa。

SPFA算法

SPFA算法的英文全称是Shortest Path Faster Algorithm,从名字上我们就看得出来这个算法的最大特点就是快。它比Bellman-ford要快上许多倍,它的复杂度是,这里的k是一个小于等于2的常数

SPFA的核心原理和Bellman-ford算法是一样的,也是对点的松弛。只不过它优化了复杂度,优化的方法也很简单,用一个队列维护了可能存在新的松弛的点。这样我们每次从这些点出发去寻找其他可以松弛的点加入队列,这里面的原理很简单,只有被松弛过的点才有可能去松弛其他的点

SPFA的代码也很短,实现起来难度很低,单单从代码上来看和普通的宽搜区别并不大。

import sys
from queue import Queue
que = Queue()

# 邻接表存储边
edges = [[]]
# 维护是否在队列当中
visited = [False for _ in range(V)]
dis = [sys.maxsize for _ in range(V)]
dis[s] = 0

que.put(s)

while not que.emtpy():
 u = que.get()
    
    for v, l in edges[u]:
        if dis[u] + l < dis[v]:
            dis[v] = dis[u] + l
            if not visited[v]:
                que.add(v)
                
 dis[u] = False

到这里,关于Bellman-ford和SPFA算法的介绍就结束了,需要提醒一点,这两个算法都不能处理负环的情况。也就是权重之和是负数的环,这样会无限松弛陷入死循环当中,可以在求最短路之前通过拓扑排序排查,也可以记录每个点进入队列的次数,通过设置阈值的方式进行排除。

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

原文链接,求个关注

- END -

算法专题 | 10行代码实现的最短路算法——Bellman-ford与SPFA的更多相关文章

  1. 《zw版·Halcon-delphi系列原创教程》简单的令人发指,只有10行代码的车牌识别脚本

    <zw版·Halcon-delphi系列原创教程>简单的令人发指,只有10行代码的车牌识别脚本 简单的令人发指,只有10行代码的车牌识别脚本      人脸识别.车牌识别是opencv当中 ...

  2. [Unity Editor]10行代码搞定Hierarchy排序

    在日常的工作和研究中,当给我们的场景摆放过多的物件的时候,Hierarchy面板就会变得杂乱不堪.比如这样:    过多的层次结构充斥在里面,根层的物件毫无序列可言,整个层次面板显示非常的杂乱不堪,如 ...

  3. 10行代码搞定移动web端自定义tap事件

    发发牢骚 移动web端里摸爬滚打这么久踩了不少坑,有一定移动web端经验的同学一定被click困扰过.我也不列外.一路走来被虐的不行,fastclick.touchend.iscroll什么的都用过, ...

  4. delphi 牛逼 了 app (已在软件界掀起波澜)10分钟10行代码做出让人惊叹的程序

    (已在软件界掀起波澜)10分钟10行代码做出让人惊叹的程序 http://v.qq.com/x/page/m0328h73bs7.html?ptag=bbs_csdn_net

  5. 如何用Python统计《论语》中每个字的出现次数?10行代码搞定--用计算机学国学

    编者按: 上学时听过山师王志民先生一场讲座,说每个人不论干什么,都应该学习国学(原谅我学了计算机专业)!王先生讲得很是吸引我这个工科男,可能比我的后来的那些同学听课还要认真些,当然一方面是兴趣.一方面 ...

  6. 10行代码,用python能做出什么骚操作

    前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:小栗子 PS:如有需要Python学习资料的小伙伴可以加点击下方链接自 ...

  7. 【啊哈!算法】算法6:只有五行的Floyd最短路算法

            暑假,小哼准备去一些城市旅游.有些城市之间有公路,有些城市之间则没有,如下图.为了节省经费以及方便计划旅程,小哼希望在出发之前知道任意两个城市之前的最短路程.         上图中有 ...

  8. 【坐在马桶上看算法】算法6:只有五行的Floyd最短路算法

            暑假,小哼准备去一些城市旅游.有些城市之间有公路,有些城市之间则没有,如下图.为了节省经费以及方便计划旅程,小哼希望在出发之前知道任意两个城市之前的最短路程.         上图中有 ...

  9. 示例 - 10行代码在C#中获取页面元素布局信息

    最近研究一个如何在网页定位验证码并截图的问题时, 用SS写了一段C#小脚本可以轻松获取页面任意元素的布局信息 (top, left, width, height). 10行功能代码, 觉得有点用, 现 ...

随机推荐

  1. SpringAOP 面向切面编程

    AOP的相关概念 AOP:全称是 Aspect Oriented Programming 即:面向切面编程. 简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改 ...

  2. 又一个小而美的Java Web框架: Solon!

    Solon 是Java世界里一个新的极易上手的Web框架.参考过 Javalin . Spring 等很多现有框架的设计. 取名自海贼王里的角色,说是希能像他一样能打 小.真的是小.最小的运行单位只有 ...

  3. 提前批笔试一道算法题的Java实现

    题目描述 这是2021广联达校招提前批笔试算法题之一. 我们希望一个序列中的元素是各不相同的,但是理想和显示往往是有差距的.现在给出一个序列A,其中难免有相同的元素,现在提供了一种变化方式,使得经过若 ...

  4. LeetCode 763划分字母区间 详解

    题目详情 字符串 S 由小写字母组成.我们要把这个字符串划分为尽可能多的片段,同一个字母只会出现在其中的一个片段.返回一个表示每个字符串片段的长度的列表. 示例 1: 输入: S = "ab ...

  5. 《Windows程序设计(第5版 珍藏版)》配书光盘

    https://pan.baidu.com/s/1ro72qQja_xTbf-Ik8b06Ng

  6. 配置 Eureka Server 集群

    简介 为了使 Eureka Server 实现高可用,我们需要为它配置集群.这样当有一台 Eureka Server 有故障时,集群中的其他 Server 可以进行代替.Eureka 集群之中的 No ...

  7. Python版常见的排序算法

    学习笔记 排序算法 目录 学习笔记 排序算法 1.冒泡排序 2.选择排序 3.插入排序 4.希尔排序 5.快速排序 6.归并排序 7.堆排序 排序分为两类,比较类排序和非比较类排序,比较类排序通过比较 ...

  8. MyISAM 和InnoDB的区别

    InnoDB和MyISAM是许多人在使用MySQL时最常用的两个表类型,这两个表类型各有优劣,视具体应用而定.基本的差别为:MyISAM类型不支持事务处理等高级处理,而InnoDB类型支持.MyISA ...

  9. virt-install 安装系统和启动虚机

    安装系统: virt-install -n x1 -r --vcpus --disk path=/home/wangjq/x1.qcow2,size=,format=qcow2,bus=virtio, ...

  10. Spring MVC 的运行流程

    1.用户发送请求到DispatcherServlet 2.DispatcherServlet调用处理器映射器(HanderMapping)找到处理器 3.处理器映射器(HanderMapping)返回 ...