Floyd算法:浅显外表下的动态规划内核

很久没遇到Floyd算法的题目了,2642. 设计可以求最短路径的图类刚好是一个典型。在实现核心算法之余,顺便整理一下算法的内核。

Floyd-Warshall’s Algorithm

Floyd-Warshall算法,简称Floyd算法,是“有向图非负权图的多源最短路”的经典算法和通用解法,以极其精炼的代码著称:

let dist be a |V| × |V| array of minimum distances initialized to ∞ (infinity)
for each edge (u, v) dodist[u][v] ← w(u, v)  // The weight of the edge (u, v)
for each vertex v dodist[v][v]0
for k from 1 to |V|for i from 1 to |V|for j from 1 to |V|if dist[i][j] > dist[i][k] + dist[k][j] dist[i][j] ← dist[i][k] + dist[k][j]end if

算法核心的三层循环,最外层的k,作为串联首位节点ij的中间节点,其必须位于最外层,否则算法的正确性就遭到了破坏。由于整个迭代的主次序是由k决定的,就好像将中间节点一个一个地“插入”进来,所以Floyd算法又被称为“插点法”。这个不能随意改变次序的三层循环,实际上正是“动态规划”所严格强调的“子状态”和“顺序”的核心体现。

插点法与动态规划

从伪码上看,我们的整个动态规划的状态转移似乎是:
d i s t [ i ] [ j ] = m i n ( d i s t [ i ] [ j ] , d i s t [ i ] [ k ] + d i s t [ k ] [ j ] ) \displaystyle \mathrm {dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])} dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j])
但如果任意翻一本教科书,会找到的其实是另一种形式的转移方程:
d i s t [ i ] [ j ] [ k ] = m i n ( d i s t [ i ] [ j ] [ k − 1 ] , d i s t [ i ] [ k ] [ k − 1 ] + d i s t [ k ] [ j ] [ k − 1 ] ) \displaystyle \mathrm { dist[i][j][k] = min(dist[i][j][k-1], dist[i][k][k-1] + dist[k][j][k-1])} dist[i][j][k]=min(dist[i][j][k1],dist[i][k][k1]+dist[k][j][k1])
我们发现第三个维度被大多数写法有意无意地隐藏掉了,这其实是很常见的优化手段。但如果没看过原始版本的转移方程,就很容易误认为只有两个维度,迭代顺序也可以调换。那怎么理解这个原始转移方程呢?dist[i][j][k]实际上代表的是,我们定义一个中间节点集合S = {0, 1, 2, ..., k-1},让这k个点以任意顺序组合插入到ij之间时,从ij的最短路径长度;不失一般性,k = 0表示中间节点为空集合,那么dist[i][j][0]就是直接从i出发到j的边的长度。所以迭代过程中,我们实际上一个一个地将节点加入到集合S中,所以这个顺序是不能调换的。注意,我们说到中间节点的任意组合,实际上意味着多少种组合呢?能否理解好这点,决定了我们能不能彻底看清Floyd算法的本质。

插点与最短路

为了理解插点法的魅力,我们先来思考一下我们在处理的问题是一个怎样规模的系统。首先,在有向图里,我们能有多少条不重复的边呢?如果节点数为n,我们从每个节点出发,都能到达另外的n-1个节点,所以边的数量最多为n(n-1)。那么,我们能构成多少条不同的路径呢?有一个直觉是,如果不对这个问题加一个限定,它将导向+∞

因为这样的“富边图”里一定有环,只要有环,路径数就是无穷多的。但是我们可以很简单地加一个限定,就是找一找不经过重复节点的路径数量。因此,从ij的不经过重复节点的路径数量是另外n-2个节点的全排列组合的总和:

C n − 2 n − 2 ( n − 2 ) ! + C n − 2 n − 3 ( n − 3 ) ! + . . . + C n − 2 0 = ∑ k = 0 n − 2 C n − 2 k k ! \displaystyle \mathrm{C_{n-2}^{n-2}(n-2)! + C_{n-2}^{n-3}(n-3)! + ... + C_{n-2}^{0} = \sum_{k=0}^{n-2} C_{n-2}^{k}k!} Cn2n2(n2)!+Cn2n3(n3)!+...+Cn20=k=0n2Cn2kk!

其中k同前文所述,代表我们引入了k个插点。我们简单放大一下,发现单源情况下路径数量应该是(n-1)!级别:

∑ k = 0 n − 2 C n − 2 k k ! ≤ ( n − 1 ) ( n − 2 ) ! = ( n − 1 ) ! \displaystyle \mathrm{ \sum_{k=0}^{n-2} C_{n-2}^{k}k! ≤ (n-1)(n-2)! = (n-1)!} k=0n2Cn2kk!(n1)(n2)!=(n1)!

如果再枚举一下起点和终点,整个“多源最短路问题”的复杂度是 O ( n ! ) \displaystyle \mathrm {O(n!)} O(n!)级别,甚至大于指数级。那么Floyd算法能在多项式时间 O ( n 3 ) \displaystyle \mathrm {O(n^3)} O(n3)内,完成对该问题的解答,并且还如此精炼,无疑是动态规划的强大魔力。此外,我们仔细检查上面这些路径也恰恰是“最短路”的备选路径,因为我们可以简单用反证法证明,在路径中引入任意一个重复节点,都必然存在比其更优的路径。
在这里插入图片描述
如上图所示,如果路径中存在重复的中间节点,因为图里没有负权边,所以上面三条路径E1E2E3一定都大于等于0,那么必然有:
E 1 + E 3 < = E 1 + E 2 + E 3 \displaystyle \mathrm { E1 + E3 <= E1 + E2 + E3} E1+E3<=E1+E2+E3
则我们必然可以通过精简掉E2这段路达到一个相对更短的路径,所以存在重复节点的路径必然不是最短路。

拆解插点法

现在我们回到插点法本身,继续讨论插点集合S和路径数量之间的关系。通过前面的分析,我们已经知道这个路径数量随着插点的增加是阶乘级别地上升,但刚开始还是相当温和的,比如在不插入点和只插入一个点时,总共的路径也就两条:
在这里插入图片描述

那么,当k=2时,又如何呢?我们发现路径开始快速膨胀。
在这里插入图片描述
这里面我们发现通过一次状态转移,我们同时继承了插入一个以下节点的所有结果——例如,蓝色的路径其实是1j目前(插一点)所有的备选路径、红色路径其实是i0目前所有的备选路径。这些备选路径中的最短值,其实已经计算过了并且存储在 d i s t [ i ] [ 1 ] \displaystyle \mathrm{dist[i][1]} dist[i][1] d i s t [ 1 ] [ j ] \displaystyle \mathrm{dist[1][j]} dist[1][j]之中了,上面的图就是已经计算过的“路径的任意组合”,而所有最优、最短路径的再组合,就是Floyd算法动态规划中状态转移的实质。因此,在没有计算完所有插k-1点的组合之前,我们是绝对不可能计算插k点的最短路的。

总结

读者可以根据上面论述继续扩展,细细品味出其中的动态规划内核之精妙,也可以帮助我们更好地理解Floyd算法,避免强行进行记忆。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/571533.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PTA-练习9

目录 实验10-4 递归实现顺序输出整数 实验10-10 十进制转换二进制 实验10-6 递归求简单交错幂级数的部分和 实验11-1-2 输出月份英文名 实验11-1-6 指定位置输出字符串 实验11-1-8 查找子串 递归的基本思路&#xff1a; 推出递归的条件或者进入递归的条件每层递归需要执行…

后端代码1

// 新增 public JsonResultVo<?> create(ApiIgnore RequestAttribute(ConstVal.REQ_USER) BaseUser baseUser,RequestBody IUTradeBuyPreserveVo iuTradeBuyPreserveVo) {//权限判断if (!baseCompanyService.dataPermission(baseUser, iuTradeBuyPreserveVo.getCompanyi…

资深用户力荐:山海鲸可视化五大常用图表解析

作为一名资深的山海鲸可视化用户&#xff0c;我在长期的数据分析和可视化过程中&#xff0c;深刻体会到了山海鲸可视化平台中各类图表的强大功能和实用性。下面&#xff0c;我将为大家介绍山海鲸可视化中常用的五个可视化图表&#xff0c;并分享我的使用心得。 一、柱状图 柱…

数据安全之路:Databend 用户策略指南

在 Databend 中&#xff0c;我们致力于保护用户的数据安全。除了身份认证之外&#xff0c;我们还提供了多种访问策略&#xff0c;包括网络策略&#xff08;Network Policy&#xff09;、密码策略&#xff08;Password Policy&#xff09;和数据脱敏策略&#xff08;Masking Pol…

全网最详细的 Ubuntu 18.04 安装Livox mid-360驱动,测试 fast_lio2

目录 一、前言 二、依赖的环境 三、 安装Livox-SDK2&#xff0c;fast_lio2 和 livox_ros_driver2 (1) 安装Livox-SDK2 (2) 安装 fast_lio2 和 livox_ros_driver2 四、mid-360 设备硬件设置 五、运行设备 六、topic信息查看 一、前言 Livox mid-360需要使用Livox-SDK2…

我们常用Linux命令总结

Linux作为一种自由和开放源代码的操作系统&#xff0c;广泛应用于各种计算机系统中&#xff0c;尤其是服务器环境。在Linux系统中&#xff0c;命令行是管理和操作系统的主要方式之一&#xff0c;熟练掌握常用的Linux命令对于系统管理员、开发人员和其他使用者来说都是至关重要的…

算法6.4-6.6DFS

一个不知名大学生&#xff0c;江湖人称菜狗 original author: Jacky Li Email : 3435673055qq.com Time of completion&#xff1a;2024.03.27 Last edited: 2024.03.27 目录 算法6.4-6.6DFS 第1关&#xff1a;算法6.5采用邻接矩阵表示图的深搜 任务描述 相关知识 编程要求…

阿里CICD流水线Docker部署,将阿里镜像私仓中的镜像部署到服务器中

文章目录 阿里CICD流水线Docker部署&#xff0c;将阿里镜像私仓中的镜像部署到服务器中一、CICD流水线的初步使用可以看我之前的两篇文章二、添加部署任务&#xff0c;进行Docker部署&#xff0c;创建一个阿里的试用主机1、选择主机部署&#xff0c;并添加服务主机2、创建免费体…

OpenHarmony之媒体组件模块简介

源码 本文基于OpenAtom OpenHarmony&#xff08;以下简称“OpenHarmony”&#xff09;3.2 Release源码foundation目录下的player_framework&#xff0c;在OpenHarmony 2.0 Release版本当中&#xff0c;这个模块的名字叫媒体组件模块&#xff0c;为了方便理解我们在本文中仍旧延…

Python Flask-Mail实现邮件发送

一、邮件发送的扩展 关于如何找到flask发送邮件的插件&#xff1f;&#xff0c;上一篇已经分享了如何找到第三方插件&#xff0c;也找到了插件flask-mail的使用文档&#xff0c;那我们就来实战吧 二、根据文档&#xff0c;总结发送邮件的流程 从文档中可以总结出发送邮件的步…

Java Web-Tomcat

Web服务器 Web服务器是一个软件程序,对HTTP协议的操作进行封装,使得程序员不必直接对协议进行操作,让Web开发更加便捷。主要功能是“提供网上信息浏览服务”。 Tomcat&#xff0c;是一个 HTTP 服务器。我们只需要在服务器中安装一个Web服务器如Tomcat&#xff0c;然后就可以将…

2024年3月GESP等级认证C++编程五级真题

2024年3月GESP认证C编程五级真题试卷 题目总数&#xff1a;27 总分数&#xff1a;100 选择题 第 1 题 单选题 唯⼀分解定理描述的内容是 ( ) ? A.任意整数都可以分解为素数的乘积 B.每个合数都可以唯⼀分解为⼀系列素数的乘积 C.两个不同的整数可以分解为相同…