图论算法:Floyd算法
Floyd算法
Floyd算法用于求图中任意两点之间的最短路径,该算法主要运用了动态规划的思想。
思考: 给你几个点与边,可以组成一张图,那么如何求得任意两点之间的最短路径呢?
我们貌似可以使用dfs或者bfs来做,那么这样做的话,我们的dfs用来求一个点到一个点之间的最短路径是可行的,但是如果是n个点?我们难道需要进行n次的dfs或者bfs吗,每次记录一个点到任意一点的最短路径,这显然是不可能的。
现在思考一个问题,假设我们的图中的每两个顶点之间的边是单向边
-
如果我们不能使用中转点:我们 1 -> 2 :那么我们就需要 找到 1->2直接的一条相连的路径,这条路径长度为e[1] [2]
-
如果我们只能使用一个中转点:我们从 1 -> 2:那么我们就需要找到 1->3 ->2(我们假设这是一个比前面 1->2路程短的路径),那么我们就可以得到: e[1] [3] + e[3] [2] 的最短路径长度
-
如果我们只能使用两个中转点:我们从 1 -> 2:那么我们就需要找到 1->3->4 ->2(我们假设这是一个比前面 1->3->2路程短的路径),那么我们就可以得到: 首先中转3:e[1] [3]+e[3] [4],然后中转4:e[1] [4] + e[4] [2] 的最短路径长度,最后的路径就是e[1] [4] + e[4] [2]
-
同理如果我们可以使用 k 个中转点。则我们便可以得到最后的最短路径就是 e[1] [k] + e[k] [2],其中 e[1] [k] 包含之前所有 k -1 个中转点的计算后的最短路径。
那么我们便可以得到一个结论:我们可以枚举 从 i 到 j 经过的前k个中转点,使得i到j的路径最短。
因此 Floyd算法的核心就是从i号顶点到j号顶点只经过前k号点的最短路程
注意:作为中转不是经过第 k 个点,而是经过了 前k 个,包含 1,2,3,4,5,6 k-1 k,即表示这 从 i到j我们可以经过总共 k 个中转点,来使得这条路径最短
算法如下:
for (int k=1;k<=n;k++) { for (int i=1;i<=n;i++) { for (int j=1;j<=n;j++) { dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]); } } }
例题:灾后重建
B 地区在地震过后,所有村庄都造成了一定的损毁,而这场地震却没对公路造成什么影响。但是在村庄重建好之前,所有与未重建完成的村庄的公路均无法通车。换句话说,只有连接着两个重建完成的村庄的公路才能通车,只能到达重建完成的村庄。
给出 B 地区的村庄数
N
N
N,村庄编号从
0
0
0 到
N
−
1
N-1
N−1,和所有
M
M
M 条公路的长度,公路是双向的。并给出第
i
i
i 个村庄重建完成的时间
t
i
t_i
ti,你可以认为是同时开始重建并在第
t
i
t_i
ti 天重建完成,并且在当天即可通车。若
t
i
t_i
ti 为
0
0
0 则说明地震未对此地区造成损坏,一开始就可以通车。之后有
Q
Q
Q 个询问
(
x
,
y
,
t
)
(x,y,t)
(x,y,t),对于每个询问你要回答在第
t
t
t 天,从村庄
x
x
x 到村庄
y
y
y 的最短路径长度为多少。如果无法找到从
x
x
x 村庄到
y
y
y 村庄的路径,经过若干个已重建完成的村庄,或者村庄
x
x
x 或村庄
y
y
y 在第
t
t
t 天仍未重建完成,则需要返回 -1
。
这道题目就是Floyd算法的模板题。
这道题目让我们求得两个村庄之间的最短路程,因此我们就可以把两个村庄看作两个点,并且中转k个点,来求得最短路径
但是如果我们采用每次询问都 进行一次floyd算法的话查找两个点的最短路径,显然是会超时的。
我们注意到有个时间的概念在里面,即每个村庄的 修复时间 是固定的,并且是会影响到我们的选择的,因为如果我们计算 1 到 3的村庄的最短路径,可能这两个村庄的修复时间在我们所给的时间内,但是如果我们选择中转,则其他的点的时间都大于我们所给的时间,所以我们不能从其他点中转过来,但是确实从其他点中转使得 1到 3 的路程会更短,因此这个时间我们便可以设置为 k 的值,即在 k时间内中转,不能超过 k时间,因此我们就可以每次询问使用一次floyd算法了,但是我们的k是固定的,我们只需要两重循环就好了。
dp[i] [j] :表示从 i 到 j 的最短距离
状态转移方程:
d
p
[
i
]
[
j
]
=
m
i
n
(
d
p
[
i
]
[
j
]
,
d
p
[
i
]
[
k
]
+
d
p
[
k
]
[
j
]
)
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j])
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j])
需要注意的几点:
- dp存储最小值,因此我们首先要初始化为 INF一个极大值
- dp[i] [i] ,即 第 i个点与第i个点之间的距离为0
- 注意 边是双向边,因此需要存储 i 到 j ,j 到 i 的距离都为边的距离
AC code
//TODO: Write code here int n,m,q; const int N=1e3+10; int nums[N],dp[N][N]; void Floyd(int k) { for (int i=0;i<n;i++) { for (int j=0;j<n;j++) { dp[i][j]=dp[j][i]=min(dp[i][j],dp[i][k]+dp[k][j]); } } } signed main() { cin>>n>>m; for (int i=0;i<n;i++) cin>>nums[i]; for (int i=0;i<n;i++) { for (int j=0;j<n;j++) { dp[i][j]=INF; } } for (int i=0;i<n;i++) { dp[i][i]=0; } for (int i=1;i<=m;i++) { int a,b,s; cin>>a>>b>>s; dp[a][b]=dp[b][a]=s; //两点之间的距离 } cin>>q; int now=0; for (int i=1;i<=q;i++) { int s1,s2,s3; cin>>s1>>s2>>s3; //根据时间进行处理 //时间是逐渐增长的,因此每次 floyd的 k 都是随时间变化的 while (nums[now]<=s3 && now<n)//目前更新的点在询问点之前 { Floyd(now);//前now个时间之前更新最短路 now++; } if (nums[s1]>s3 || nums[s2]>s3) { cout<<-1<<endl; } else { if (dp[s1][s2]==INF) cout<<-1<<endl; else cout<<dp[s1][s2]<<endl; } } #define one 1 return 0; }
本文来自博客园,作者:hugeYlh,转载请注明原文链接:https://www.cnblogs.com/helloylh/p/17209605.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)