当前位置: 首页 > news >正文

【数据结构与算法】图 Floyd算法

相关题目: 

1334. 阈值距离内邻居最少的城市 - 力扣(LeetCode)

资料 : 

Floyd算法原理及公式推导 - 知乎

Floyd 算法是一种经典的动态规划算法,用与求解图中所有顶点之间的最短短路路径。它由Robert Floyd  于1962 年提出,核心思想是通过“中间顶点”逐步松弛路径长度,最终得到任意两点间的最短距离。

算法核心 : 

图的类型:无向图 ,有向图(注意边的方向性)

边权特性: 支持负权边,但是不允许“包含负权边的回路”,否则会导致路径长度无线减小,无法收敛。

图的存储:  

使用邻接矩阵存储,用于快速访问任意两点间的边权。

算法原理步骤

动态规划 状态定义  

定义 dp[k][i][j]=表示只允许使用前k个节点作为中间节点 ,顶点 i 到顶点j 的最短距离。

根据状态转移: 

不选择第k个节点 作为中间节点:

dp[k][i][j]=dp[k-1][i][j]

选择第k个顶点组委中间节点:路径拆分为i->k->j

dp[k][i][j] =dp[k-1][i][j]+dp[k-1][i][j].

状态转移方程为:

dp[k][i][j]=min(dp[k-1][i][j],dp[k-1][i][k]+dp[k-1][k][j])

空间优化(关键)

观察dp[k][...]   仅依赖dp[k-1][...]  ,因此无需存储所有k 层状态,可以直接用二维数组dist覆盖更新

(k 作为外层循环,每次更新dist[i]j[ 时复用之前的结果])。

实现步骤

假设  图中有n个顶点,邻接矩阵初始化为dist ,步骤如下: 

初始化邻接矩阵

对顶点i, dist[i][i]=0  自身到自身的距离为0 

如果顶点i和j 直接有直接的边,权重为w 则dist[i][j]=w

如果顶点i 和j 无直接边,dist[i][j]=\infty , 表示不可达,通常用一个极大值如10^9

外层循环(枚举中间节点k)

遍历所有可能的中间顶点k(从1到n)

中间循环: 枚举起点i:

 遍历所有可能的起点i (从1到n)

内侧循环:枚举终点 j

遍历所有可能的终点j , (1到 n ),执行松弛操作,

dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])

(可选)检测负权回路

算法结束后,若存在 (dist[i][i] < 0),则说明图中存在包含顶点 i 的负权回路(因为自身到自身的距离不可能为负)

代码 

c++

#include <iostream>
#include <vector>
#include <climits>
using namespace std;const int INF = INT_MAX / 2;  // 避免加法溢出int main() {int n, m;  // n: 顶点数, m: 边数cin >> n >> m;// 1. 初始化邻接矩阵vector<vector<int>> dist(n + 1, vector<int>(n + 1, INF));for (int i = 1; i <= n; ++i) {dist[i][i] = 0;  // 自身到自身距离为0}// 读入边:u -> v,权值 wfor (int i = 0; i < m; ++i) {int u, v, w;cin >> u >> v >> w;dist[u][v] = w;  // 若为无向图,需额外添加 dist[v][u] = w}// 2. Floyd 算法核心(k: 中间顶点,i: 起点,j: 终点)for (int k = 1; k <= n; ++k) {for (int i = 1; i <= n; ++i) {for (int j = 1; j <= n; ++j) {// 若 i->k 或 k->j 不可达,则跳过(避免 INF + INF 溢出)if (dist[i][k] != INF && dist[k][j] != INF) {dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);}}}}// 3. 输出所有顶点间的最短距离cout << "所有顶点间的最短距离:" << endl;for (int i = 1; i <= n; ++i) {for (int j = 1; j <= n; ++j) {if (dist[i][j] == INF) {cout << "INF ";  // 不可达} else {cout << dist[i][j] << " ";}}cout << endl;}// 4. 检测负权回路bool has_negative_cycle = false;for (int i = 1; i <= n; ++i) {if (dist[i][i] < 0) {has_negative_cycle = true;break;}}if (has_negative_cycle) {cout << "图中存在负权回路!" << endl;}return 0;
}

python 

import sys
# 避免类似 C++ 中 INT_MAX 溢出问题,使用一个足够大的数表示“不可达”
INF = float('inf')def main():# 读取输入(顶点数 n,边数 m)# 注:Python 中 input() 读取单行,需拆分字符串获取整数;若输入较大可改用 sys.stdin 提速n, m = map(int, sys.stdin.readline().split())# 1. 初始化邻接矩阵:dist[i][j] 表示顶点 i 到 j 的初始距离# 顶点编号从 1 开始(与 C++ 保持一致,避免索引偏移)dist = [[INF] * (n + 1) for _ in range(n + 1)]# 自身到自身的距离为 0for i in range(1, n + 1):dist[i][i] = 0# 读入 m 条边:u -> v,权值 w(有向边)for _ in range(m):u, v, w = map(int, sys.stdin.readline().split())# 若存在多条从 u 到 v 的边,保留权值最小的一条(与原 C++ 逻辑一致)dist[u][v] = w# 【若为无向图】需添加以下一行(无向边等价于双向有向边):# dist[v][u] = w# 2. Floyd 算法核心:三层循环枚举中间顶点、起点、终点# k:中间顶点(允许经过前 k 个顶点时更新最短路径)for k in range(1, n + 1):# i:路径起点for i in range(1, n + 1):# j:路径终点for j in range(1, n + 1):# 跳过不可达的情况(避免 INF + INF 无意义计算)if dist[i][k] != INF and dist[k][j] != INF:# 松弛操作:更新 i->j 的最短距离if dist[i][j] > dist[i][k] + dist[k][j]:dist[i][j] = dist[i][k] + dist[k][j]# 3. 输出所有顶点间的最短距离(格式与 C++ 一致)print("所有顶点间的最短距离:")for i in range(1, n + 1):row = []for j in range(1, n + 1):if dist[i][j] == INF:row.append("INF")else:row.append(str(dist[i][j]))# 用空格连接一行的元素,与 C++ 输出格式对齐print(' '.join(row))# 4. 检测负权回路:若存在顶点 i 满足 dist[i][i] < 0,说明有负权回路has_negative_cycle = Falsefor i in range(1, n + 1):if dist[i][i] < 0:has_negative_cycle = Truebreakif has_negative_cycle:print("图中存在负权回路!")if __name__ == "__main__":main()

不可达值(INF): Python 中用 float('inf') 表示 “无穷大”,比 C++ 的 INT_MAX/2 更直观,且天然避免整数溢出问题(无需担心 INF + INF 超出范围)。

邻接矩阵初始化: 用 Python 列表推导式 [[INF]*(n+1) for _ in range(n+1)] 创建 (n+1)×(n+1) 的矩阵(顶点编号从 1 开始,与 C++ 一致,减少逻辑偏移)。

输入处理: 使用 sys.stdin.readline() 替代 input(),处理大量输入时效率更高(适配题目中可能的大规模图数据);通过 map(int, ...split()) 拆分输入字符串为整数,与 C++ 的 cin >> 逻辑一致。 核心松弛操作: 保留三层循环的顺序(k→i→j),仅将 C++ 的 min() 函数替换为 Python 的条件判断(if dist[i][j] > ...),逻辑完全等价,且更符合 Python 代码习惯。

输出格式: 用列表 row 收集每行的输出内容,最后通过 ' '.join(row) 用空格连接,确保输出格式与 C++ 一致(如 “INF” 对应不可达,数字对应最短距离)。 负权回路检测: 保留原逻辑 —— 遍历所有顶点 i,若 dist[i][i] < 0,说明存在经过 i 的负权回路(因为 “自身到自身的最短路径” 不可能为负)。

示例 

示例1

输入(3 个顶点,4 条有向边):

plaintext

3 4
1 2 2
1 3 6
2 3 1
3 1 -4

输出:

所有顶点间的最短距离: 0 2 3 -3 0 1 -4 -2 0 图中存在负权回路!

该示例中,dist[1][1] = 0、dist[2][2] = 0、dist[3][3] = 0 看似无负,但实际计算过程中会发现循环 1→2→3→1 的总权值为 2+1+(-4) = -1 < 0,最终代码会正确检测出负权回路。

示例2

无负权边的有向图示例

中间顶点如何优化最短路径

示例场景:4 个顶点的有向图

假设我们有一个包含 4 个顶点(编号 1~4)的有向图,边的连接和权值如下(边的方向和权重是核心):

  • 1 → 2,权值 3
  • 1 → 3,权值 6
  • 2 → 3,权值 2
  • 2 → 4,权值 5
  • 3 → 4,权值 1
  • 4 没有出边

第一步:明确输入格式

根据代码的输入要求(先输入顶点数 n 和边数 m,再输入 m 条边的起点、终点、权值),该示例的输入如下:

plaintext

4 5
1 2 3
1 3 6
2 3 2
2 4 5
3 4 1

第二步:算法执行逻辑拆解(核心步骤)

我们会按 Floyd 算法的三层循环(中间顶点 k → 起点 i → 终点 j),逐步展示邻接矩阵 dist 的更新过程,理解 “中间顶点如何缩短路径”。

1. 初始化邻接矩阵

初始时,dist[i][j] 的规则:

  • 自身到自身:dist[i][i] = 0
  • 有直接边:dist[i][j] = 边权
  • 无直接边:dist[i][j] = INF(用  表示)

初始 dist 矩阵(行是起点,列是终点):

起点 \ 终点1234
1036
2025
301
40

2. 枚举中间顶点 k=1(允许经过顶点 1 优化路径)

此时检查所有 i→j,看是否能通过 i→1→j 缩短路径。 由于顶点 1 的出边只有 1→2、1→3,且大部分 i→1 不可达(如 i=2,3,4 到 1 是 ∞),因此矩阵无更新。

3. 枚举中间顶点 k=2(允许经过顶点 2 优化路径)

检查所有 i→j,看是否能通过 i→2→j 缩短路径,关键更新如下:

  • 对于 i=1, j=3:原路径 1→3 权值 6;新路径 1→2→3 权值 3+2=5 → 更新 dist[1][3] = 5
  • 对于 i=1, j=4:原路径 1→4 是 ;新路径 1→2→4 权值 3+5=8 → 更新 dist[1][4] = 8

更新后的 dist 矩阵:

起点 \ 终点1234
10358
2025
301
40
4. 枚举中间顶点 k=3(允许经过顶点 3 优化路径)

检查所有 i→j,看是否能通过 i→3→j 缩短路径,关键更新如下:

  • 对于 i=1, j=4:原路径 1→4 权值 8;新路径 1→3→4 权值 5+1=6 → 更新 dist[1][4] = 6
  • 对于 i=2, j=4:原路径 2→4 权值 5;新路径 2→3→4 权值 2+1=3 → 更新 dist[2][4] = 3

更新后的 dist 矩阵:

起点 \ 终点1234
10356
2023
301
40
5. 枚举中间顶点 k=4(允许经过顶点 4 优化路径)

由于顶点 4 没有出边(所有 4→j 除了自身都是 ),因此矩阵无更新

第三步:代码输出结果

将上述输入代入 Python 代码,最终输出如下(包含所有顶点间的最短距离,且无负权回路):

plaintext

所有顶点间的最短距离:
0 3 5 6
INF 0 2 3
INF INF 0 1
INF INF INF 0

结果解读

输出矩阵的每一行代表 “从当前起点到所有终点的最短距离”:

  • 起点 1 到各终点:1→1(0)、1→2(3)、1→3(5,1→2→3)、1→4(6,1→2→3→4)
  • 起点 2 到各终点:2→1(不可达)、2→2(0)、2→3(2)、2→4(3,2→3→4)
  • 起点 3 到各终点:3→1/2(不可达)、3→3(0)、3→4(1)
  • 起点 4 到各终点:4→1/2/3(不可达)、4→4(0)

这与我们手动拆解的 “最优路径” 完全一致,验证了代码的正确性。

额外测试:含负权边(无负权回路) 若在上述示例中添加一条边 3→1,权值 -2(无负权回路),输入变为: plaintext 4 6 1 2 3 1 3 6 2 3 2 2 4 5 3 4 1 3 1 -2 代码会检测到 “无负权回路”,且更新后的 dist[2][1] 会变为 2→3→1 的权值 2 + (-2) = 0,dist[3][1] = -2 等,进一步体现 Floyd 算法对负权边的支持。


文章转载自:

http://IeGLjl8s.sfwcx.cn
http://EThZogKu.sfwcx.cn
http://gzljxvVM.sfwcx.cn
http://GSJzJzVm.sfwcx.cn
http://oQ1c9RdT.sfwcx.cn
http://mRTXglDr.sfwcx.cn
http://91ho55Ky.sfwcx.cn
http://hw72OpeC.sfwcx.cn
http://R5mvbJYq.sfwcx.cn
http://9443yKNT.sfwcx.cn
http://LnhfIO3X.sfwcx.cn
http://2PBHHtYH.sfwcx.cn
http://svfHrvkv.sfwcx.cn
http://Oe2rIopb.sfwcx.cn
http://i0BVSJQG.sfwcx.cn
http://FysNuBxE.sfwcx.cn
http://1v9hc0RE.sfwcx.cn
http://piuBOcAg.sfwcx.cn
http://UBtc2MvO.sfwcx.cn
http://zemaA3zZ.sfwcx.cn
http://oJihVvgd.sfwcx.cn
http://bPaxVr3a.sfwcx.cn
http://1uTyV8pD.sfwcx.cn
http://3WDVk946.sfwcx.cn
http://28tsstwd.sfwcx.cn
http://XAgUMS41.sfwcx.cn
http://XGlbeO9J.sfwcx.cn
http://HXoCjbUj.sfwcx.cn
http://V7y8xjcI.sfwcx.cn
http://etFO98S5.sfwcx.cn
http://www.dtcms.com/a/383422.html

相关文章:

  • 代码随想录算法训练营第十一天--二叉树2 || 226.翻转二叉树 / 101.对称二叉树 / 104.二叉树的最大深度 / 111.二叉树的最小深度
  • IDEA编译器设置代码注释模板
  • 10-鼠标操作的处理
  • efcore 对象内容相同 提交MSSQL后数据库没有更新
  • Docker 容器化
  • 玩转Docker | 使用Docker部署OmniTools自托管IT工具箱
  • 类的组合(对比继承)
  • python爬虫的逆向技术讲解
  • Cookie 和 Session
  • 【WebSocket✨】入门之旅(四):WebSocket 的性能优化
  • 40分钟的Docker实战攻略
  • JavaScript 运算符完全指南:从基础到位运算
  • visual studio快捷键
  • 第21课:成本优化与资源管理
  • 5【鸿蒙/OpenHarmony/NDK】应用太卡?用 Node-API 异步任务解决:从卡顿根源到流畅方案
  • 利用OpenCV进行对答题卡上的答案进行识别的案例
  • 如何用 Rust 实现的基础屏幕录制程序?
  • 认知语义学隐喻理论对人工智能自然语言处理中深层语义分析的赋能与挑战
  • 常见索引失效场景及原因分析(含示例)
  • 嵌入式Linux常用命令
  • xtuoj Rectangle
  • C++内存管理:new与delete的深层解析
  • Nginx 实战系列(十)—— 搭建LNMP环境与部署Discuz!社区论坛指南
  • 计算机视觉案例分享之答题卡识别
  • 端口打开与服务可用
  • 如何解决 pip install 安装报错 ModuleNotFoundError: No module named ‘requests’ 问题
  • 使用Docker和虚拟IP在一台服务器上灵活部署多个Neo4j实例
  • Web前端面试题(2)
  • 硬件开发_基于物联网的仓鼠饲养监测系统
  • 资产负债表、利润表、经营现金流、统计指标计算程序