今天是算法数据结构专题的第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. 8月1日起全部无版号游戏下架,ios手游想上架看这里!

      在苹果至中国游戏开发者的邮件中声明:如果开发者不能在7月31日前提交版号及相关文件,付费游戏将不可以在中国AppStore供应.也就是说:   从8月1日开始,苹果将正式下架全部.所有的ios付费 ...

  2. excel-格式处理

    问题[1]:将excl中数据导出txt,并且每列之间距离一个空格 在C1(任意空列) 输入=A1&" "&B1" "中间是一个半角英文空格下拉 ...

  3. Hive对字段进行urlDecode

    最近项目中需要对埋点日志hive表进行分析,并且按一定的规则统计出来满足要求的用户pin.本来以为是一件比较简单的事,结果在查看导出的词表时发现很多带有"%"的明显具有url en ...

  4. [转]解决The requested resource is not available的方法

    此博文为转载博文,首先感谢原作者 HTTP Status 404(The requested resource is not available)异常主要是路径错误或拼写错误造成的,请按以下步骤逐一排 ...

  5. Zookeeper学习(二)

    一.Znode节点属性 dataVersion 数据版本, 每次当 Znode 中的数据发生变化的时候, dataVersion都会自增一下cversion 节点版本, 每次当 Znode 的节点发生 ...

  6. C#LeetCode刷题之#705-设计哈希集合​​​​​​​(Design HashSet)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/4114 访问. 不使用任何内建的哈希表库设计一个哈希集合 具体地说 ...

  7. Flutter 容器(4) - Container

    Container 类似于HTML中的div标签 import 'package:flutter/material.dart'; class AuthList extends StatelessWid ...

  8. CSS动画实例:旋转的圆角正方形

    在页面中放置一个类名为container的层作为效果呈现容器,在该层中再定义十个名为shape的层层嵌套的子层,HTML代码描述如下: <div class="container&qu ...

  9. 设计模式:建造者模式及在jdk中的体现,建造者模式和工厂模式区别

    0.背景 建造模式(Builder模式) 假如有一个需求:盖房子,盖房子过程是一致的:打桩.砌墙.封顶.但是房子是各式各样的,最后盖出来的房子可能是高楼或别墅. 根据直接的思路,不用设计模式思想,我们 ...

  10. Goland 生成可执行文件

    Goland通过调用go build 生成可执行文件. 默认Goland是可以执行程序,但你找不到可执行文件. 你需要自定义配置文件. 创建go build配置文件 Run kind 选Directo ...