今天是算法数据结构专题的第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. 基于asp.net core 从零搭建自己的业务框架(三)

    前言 根据业务处理部分,单体马上就能得知错误与否,快速做出处理,而分布式系统,会因为各种原因,无法如同单体一样立刻处理,所以这个时候需要 处理异常 的,做 补偿.转移.人工干预. 当然也可以直接在消费 ...

  2. 【NOIP2013】火柴排队 题解(贪心+归并排序)

    前言:一道水题. ----------------------- 题目链接 题目大意:给出数列$a_i$和$b_i$,问使$\sum_{i=1}^n (a_i-b_i)^2$最小的最少操作次数. 首先 ...

  3. mac 教你如何在Mac上搭建自己的服务器——Nginx

    WHAT 本篇主要是基于Nginx在Mac上搭建自己的服务器. 我相信很多朋友肯定是第一次听到Nginx,关于它具有怎样的传奇,这儿肯定说不完也说不透. 有兴趣的朋友可以自行google或者baidu ...

  4. WPF桌面程序在请求接口时如何防止被常用的抓包软件Fiddler抓包

    问题:在我开发了一个WPF桌面应用程序的时候,由于涉及到登录等等操作通过Fiddler可以很直观的看到账号密码.首先问题有两点:1.数据提交的时候对于密码等重要的数据没有进行加密操作.2.没有防止抓包 ...

  5. Android ExpandListView的用法(补上昨天的)(今天自习)

    今天自习写ExpandListView的作业,昨天没写博客就是去写作业去了. 今天来说昨天内容吧! 其实ExpandListView和ListView的用法大同小异. 首先就是创建一个自己的适配器(现 ...

  6. 精讲RestTemplate第3篇-GET请求使用方法详解

    本文是精讲RestTemplate第3篇,前篇的blog访问地址如下: 精讲RestTemplate第1篇-在Spring或非Spring环境下如何使用 精讲RestTemplate第2篇-多种底层H ...

  7. 用 Python 可以实现侧脸转正脸?我也要试一下!

    作者 | 李秋键 责编 | Carol 封图 | CSDN 下载自视觉中国 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案例 ...

  8. 解放双手!用 Python 控制你的鼠标和键盘

    在工作中难免遇到需要在电脑上做一些重复的点击或者提交表单等操作,如果能通过 Python 预先写好相关的操作指令,让它帮你操作,然后你自己去刷网页打游戏,岂不是很爽?] 很多人学习python,不知道 ...

  9. 「MoreThanJava」Day 5:面向对象进阶——继承详解

    「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...

  10. 致敬平凡的程序员--《SOD框架“企业级”应用数据架构实战》自序

    “简单就是美” “平凡即是伟大” 上面两句话不知道是哪位名人说的,又或者是广大劳动人民总结的,反正我很小的时候就常常听到这两句话,这两句话也成了我的人生格言,而且事实上我也是一个生活过得比较简单的平凡 ...