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.mzph.cn/news/774046.shtml

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

相关文章

Selenium自动化测试面试题全家桶

1、什么是自动化测试、自动化测试的优势是什么&#xff1f; 通过工具或脚本代替手工测试执行过程的测试都叫自动化测试。 自动化测试的优势&#xff1a; 1、减少回归测试成本 2、减少兼容性测试成本 3、提高测试反馈速度 4、提高测试覆盖率 5、让测试工程师做更有意义的…

10个你必须知道的浏览器指纹检测工具,保护你的隐私安全

在当前的数字时代&#xff0c;个人隐私保护变得越来越重要&#xff0c;特别是对于互联网用户来说。有一种叫做“浏览器指纹”的技术&#xff0c;它能悄悄收集我们使用的浏览器和设备的各种细节信息。这本是为提供个性化服务&#xff0c;但对那些需要在不同平台同时管理多个账号…

python项目子模块配置

创建模块子应用 1.在项目中新建一个apps的目录&#xff0c;用于存放所有子模块应用 2.在apps包下创建所需应用 注册模块子应用 1.在主模块里面寻找到配置文件 2.在配置文件中找到 INSTALLED_APPS&#xff0c;添加相应路径apps.users Tips: 由于每次添加都要输入前缀apps.会…

C++|类封装、类的分文件编写练习:设计立方体类、点和圆的关系

文章目录 练习案例1&#xff1a;设计立方体类CPP代码 练习案例2:点和圆的关系CPP代码 代码总结类的分文件编写 练习案例1&#xff1a;设计立方体类 设计立方体类(Cube) 求出立方体的面积和体积 分别用全局函数和成员函数判断两个立方体是否相等。 CPP代码 class Cube { pub…

kubernetes名称空间和资源配额

名称空间 一.名称空间概念二.Namespace使用场景三.名称空间基本操作1.创建Namespace两种方式2.Namespace资源限额 一.名称空间概念 Kubernetes 支持多个虚拟集群&#xff0c;它们底层依赖于同一个物理集群。 这些虚拟集群被称为命名空间。 命名空间namespace是k8s集群级别的资…

Thread类中start方法和run方法的源码简单解读,联系和区别

我们可以打开idea,按住ctrl将光标移至所查方法上,单击右键,即可查看这两个方法的源码: 1. start方法源码 我们从上至下分析一下: 这个threadStatus是一个int型的变量来表示线程是否开始,0为没有开始,非0为开始,因此当threadStatus不为0时,会抛出非法开始线程的异常. group对象…

可怜的百度人

可怜的百度股民 注意&#xff0c;这里说的是持有百度股票的股民&#xff0c;不是百度&#xff0c;百度没啥好可怜的。 前天&#xff08;3月25日&#xff09;中午&#xff0c;财联社爆料百度和 Apple 达成合作&#xff0c;百度将为苹果今年发布的 iPhone16、Mac 系统和 iOS18 提…

气体放电的基本物理过程

本篇为本科课程《高电压工程基础》的笔记。 和固体液体介质相比&#xff0c;气体绝缘有不老化的有点&#xff0c;而且击穿后具有完全的绝缘自恢复特性&#xff0c;是绝缘部分的重点。 带电质点的产生与消失 中性气体不到点&#xff0c;但是由于宇宙射线和地壳中的放射性物质…

如何做好位移计保养和维修工作

当涉及到保养和维修位移计时&#xff0c;我们需要考虑一系列的因素&#xff0c;包括正确的使用方法、定期的保养程序以及必要的维修步骤。位移计是一种用于测量物体相对位置变化的仪器&#xff0c;因此其准确性和可靠性对于许多工程和科学应用至关重要。以下是一些关于如何保养…

【前端面试3+1】01闭包、跨域、路由模式

一、对闭包的理解 定义&#xff1a; 闭包是指在一个函数内部定义的函数&#xff0c;并且该内部函数可以访问外部函数的变量。闭包使得函数内部的变量在函数执行完后仍然可以被访问和操作。 特点&#xff1a; 闭包可以访问外部函数的变量&#xff0c;即使外部函数已经执行完毕。…

2023最新版Android studio安装入门教程(非常详细)从零基础入门到精通,看完这一篇就够了

目录 JDK安装与配置 一、下载JDK 二、JDK安装 三、JDK的环境配置 四、JDK的配置验证 Android studio安装 Android studio连接手机真机调试&#xff08;以华为鸿蒙为例&#xff09; 一、新建一个android项目 二、进入项目面板 三、配置Android Studio 四、安装手机驱…

抽象类和接口(1)(抽象类部分)

抽象类概念 在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c;如果一个类中没有包含足够的信息来描绘一个具体的对象&#xff0c;这样的类就是抽象类. 此外还有抽象方法这个概念 …

拿到今日现货白银价格 如何开始分析?

很多投资者看到近期现货白银的强劲涨势&#xff0c;并且开户入场。但入场之后&#xff0c;他们发现对如何找到机会还不甚了解。比方说我们拿到今日现货白银价格要如何开始分析呢&#xff0c;很多新入场的投资者根本没有头绪&#xff0c;下面我们就来讨论一下相关的方法。 我们可…

Java入门之数据类型

一、数据类型 基本数据类型 &#xff08;1&#xff09;如果要定义“long类型的变量要在数值后面加一个L作为后缀” &#xff08;2&#xff09;如果要定义float类型的变量的时候数据值也要加一个作为后缀 小结&#xff1a; 练习 内容&#xff1a; 姓名&#xff1a;巴巴托斯 &…

【比特币】比特币的奥秘、禁令的深层逻辑与风云变幻

导语&#xff1a; 比特币(Bitcoin)&#xff0c;这个充满神秘色彩的数字货币&#xff0c;自诞生以来便成为各界瞩目的焦点。它背后所蕴含的Mining机制、禁令背后的深层逻辑以及市场的风云变幻&#xff0c;都让人欲罢不能。今天&#xff0c;我们将深入挖掘比特币的每一个角落&…

docker将本地镜像pull到阿里云和registry

目录 一、上次到阿里云服务器 1、制作一个带有vim功能的Ubuntu镜像 2、在阿里云上面创建镜像仓库 3、从阿里云仓库中上传和拉取镜像 二、上传镜像到本地私有库registry 1、下载镜像docker registry 2、运行私有库registry&#xff0c;相当于本地有个私有docker hub。 3…

Rust使用feature特性和条件编译,以及常用feature使用说明

Cargo Feature 是非常强大的机制&#xff0c;可以为大家提供条件编译和可选依赖的高级特性&#xff0c;可以为你省下不少的代码量来判断操作系统和条件编译等功能。rust官方条件编译文档&#xff1a;Conditional compilation - The Rust Reference features特性 Featuure 可以…

零基础学习挖掘PHP网站漏洞

教程介绍 本套课程&#xff0c;分为三个阶段&#xff1a;第一阶段&#xff1a;基础篇 学习PHP开发的基础知识&#xff0c;对PHP常见的漏洞进行分析&#xff0c;第二阶段&#xff1a;进阶篇 实战PHP漏洞靶场&#xff0c;了解市面上的PHP主流网站开发技术&#xff0c;并对市面上…

RocketMQ 流数据库解析:如何实现一体化流处理?

作者&#xff1a;林清山&#xff08;隆基&#xff09; 前言&#xff1a; 从初代开源消息队列崛起&#xff0c;到 PC 互联网、移动互联网爆发式发展&#xff0c;再到如今 IoT、云计算、云原生引领了新的技术趋势&#xff0c;消息中间件的发展已经走过了 30 多个年头。 目前&a…

AI+云平台|全闪云底座迎战

AI融万物之势席卷而来 人工智能&#xff08;Artificial Intelligence&#xff09;&#xff0c;英文缩写为AI。它是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。 行业特点 AI场景中80%以上是小文件&#xff0c;以非结构化数据为…