【B3647】Floyd 算法
【B3647】Floyd 算法
Preface
这是我在大学“数据结构”课程的“翻转课堂”活动的授课任务,即向同学讲解 Floyd 算法。因为课堂的听众不全有竞赛经历,所以我事实上在讲课的前半部分通过几道例题简单介绍了动态规划算法作为引入,在本文中略去。如果你还不了解动态规划和“阶段”“状态”等相关理论,可以先通过课件了解动态规划。完整课件可以点击这里下载。
对本文和课件的转载必须注明作者和出处。
Floyd-Warshal Algorithm 是一个基于动态规划的全源最短路算法。它可以高效地求出图上任意两点之间的最短路。
Preliminaries
如无特殊约定,本文用
我们用如下的邻接矩阵结构存图:
约定忽略图的自环,重边仅保留权值最小的一条。
A Rough Work
我们首先考虑边权均非负的情况。
采用动态规划的方法解决这一问题:设
初始状态显然有
考虑计算
- 不经过编号为
k 的顶点。则此时的最短路就是f_{k - 1, u, v} ; - 经过编号为
k 的顶点。那么我们此时的最短路会通过k 号顶点。我们把u 到v 的路径拆成u\rightarrow k 和k \rightarrow v 两条。注意到此时u 到k 的路径上的顶点显然应该都小于k (因为没有负边,走一个点超过两次不会得到任何正收益),k 到v 的路径同理。于是这两条路径的最短路分别是f_{k-1, u, k} 和f_{k-1, k, v} ,如图:
综合两种情况,转移就是
容易写出代码:
for k := 1 to n:
for u := 1 to n:
for v := 1 to n:
f(k,u,v) := min{f(k-1,u,v), f(k-1,u,k)+f(k-1,k,v)}
在这里,
Space Cost Optimization
A Classic Skill
易见在上一部分里我们提出的算法时空复杂度均为
考察转移:
采用经典的“滚动数组”的技巧,令
则转移可以写成:
注意每个阶段的转移结束之后(即
for k := 1 to n:
for u := 1 to n:
for v := 1 to n:
g(1,u,v) := min{g(0,u,v), g(0,u,k)+g(0,k,v)}
for u := 1 to n:
for v := 1 to n:
g(0,u,v) := g(1,u,v)
这样,我们只需要一个大小为
Further Optimization
考察原始的转移方程:
不难发现:
于是滚动数组后的转移可以写成:
这就是说,在转移
我们直接令
写作代码就是:
for k := 1 to n:
for u := 1 to n:
for v := 1 to n:
g(u,v) := min{g(u,v), g(u,k)+g(k,v)}
这就是我们所熟悉的 Floyd 算法的最常见形式。其时间复杂度为
Application on Graphs with Negative Weight
Graphs Without Negative Circles
我们已经得到了非负权图上的 Floyd 算法。是时候尝试把它拓展到负权图上了。
我们首先考虑图上没有负圈的情况。此时任意两点之间都有唯一的最短路。
考察在非负权图上的转移:
为了计算
f_{k,u,v} ,此时的最短路有两种方案:
- 不经过编号为
k 的顶点。则此时的最短路就是f_{k - 1, u, v} ;- 经过编号为
k 的顶点。那么我们此时的最短路会通过k 号顶点。我们把u 到v 的路径拆成u\rightarrow k 和k \rightarrow v 两条。注意到此时u 到k 的路径上的顶点显然应该都小于k ,k 到v 的路径同理。此时的最短路是f_{k - 1, u, k} + f_{k - 1, k, v} 。
考察第二条转移成立的条件是「
结论是成立。因为如果经过了
所以非负权图上的 Floyd 算法可以直接推广到无负圈的负权图上。
Graphs with Negative Circles
考察有负圈的情况,Floyd 算法能否确定某张图是否存在负圈?又能否确定那些不受负圈影响的顶点对之间的最短路?
先跑一遍 Floyd 算法,把结果记下来。以结果为初始边权再跑一遍 Floyd 算法(也就是在第一次 Floyd 跑完的数组上直接再跑一轮)。对比两次算法的结果。如果两个顶点对