今天是算法数据结构专题的第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. 解决IIS发布时CS0016未能写入输出文件错误

    今天遇到一个将asp.net项目部署到IIS后访问的时候报的一个错误: 在网上查询了相关资料后,解决方法如下: 找到C:\Windows\下的temp文件,右键属性>安全>编辑,给其中II ...

  2. 2017面向对象程序设计(Java)第十七周助教工作总结

            本学期已接近尾声,java课程也即将结束.经过一学期的java学习,相信大家已经从最初的懵懂.困惑渐渐的走向了柳暗花明,并对java的体系结构有了更加清晰的认识.但一学期的学习是远远不 ...

  3. Linux-sed命令使用笔记

    Linux的sed命令和python脚本一起,可以对文本进行快速的修改.比如在删除日志的时候,python写出固定日期删除脚本,再用sed循环将python脚本的日期修改调用,就可以批删除指定日期的日 ...

  4. 阿里云ECS服务器购买流程

    先说说什么是阿里云服务器ECS?云服务器(Elastic Compute Service,即弹性计算服务,简称ECS)是阿里云提供的性能卓越.稳定可靠.弹性扩展的IaaS(Infrastructure ...

  5. Dubbo系列之 (二)Registry注册中心-注册(1)

    引导 dubbo的服务的注册与发现,需要通过第三方注册中心来协助完成,目前dubbo支持的注册中心包括 zookeeper,consul,etcd3,eureka,nacas,redis,sofa.这 ...

  6. C#LeetCode刷题之#1-两数之和(Two Sum)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3762 访问. 给定一个整数数组和一个目标值,找出数组中和为目标值 ...

  7. Centos搭建go环境以及go入门

    引言 本文主要聚焦于 如何在centos上搭建go环境以及go入门, 包括搭建go环境,hello world运行, 创建包等操作,初步入门go语言. 安装环境 在管理员权限下, 也就是root用户 ...

  8. Goland 生成可执行文件

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

  9. .NET Core + K8S + Apollo 玩转配置中心

    1.引言 Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境.不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限.流程治理等特性,适用于微服务配置管理 ...

  10. python基础 Day11

    python Day11 函数中默认参数的陷阱 只针对默认参数是可变的数据类型(如果你的默认参数指向的是可变的数据类型,那么你无论调用多少次默认参数,都是同一个) def test(name,list ...