Floyd算法
一、算法原理
相关博文【已经变成VIP专享了= =】:http://t.csdnimg.cn/xupz2
Floyd算法是求解多源最短路(多对多)的算法,即确定每个节点(起点)到其他节点(终点)的最短路
-
算法适用于有向图、无向图,允许边的权重为负,但是负边构成的回路(环)的权重之和不能为负(负环)
-
算法通过不断“插点”的方式,更新节点之间的最短路,当所有节点都考虑一遍(“插点”)之后,图中所有节点之间的最短路得以确定。
举例说明:4个节点组成的无向有权图,定义节点之间的距离矩阵**:
d
[
i
]
[
j
]
=
{
0
i
=
j
l
i
,
j
相邻
(
l
为路段长度
)
+
∞
i
,
j
不相邻
(
求解时可以用足够大的正数表示
)
\begin{equation} d[i][j]=\left\{ \begin{aligned} 0 & \quad i=j\\ l & \quad i,j相邻(l为路段长度)\\ +\infty & \quad i,j不相邻(求解时可以用足够大的正数表示) \end{aligned} \right . \end{equation}
d[i][j]=⎩
⎨
⎧0l+∞i=ji,j相邻(l为路段长度)i,j不相邻(求解时可以用足够大的正数表示)
“插点”及距离矩阵更新过程
Step1:考虑节点A
- 对于节点A,到其他节点(包含自身)的距离保持不变;
- 对于节点B,增加了“新路径B-A-C”,新路径的长度为B-A、A-C的长度之和( 1 + 2 = 3 1+2=3 1+2=3),小于B-C原有的长度( + ∞ +\infty +∞),因此更新B-C的距离为3;
- 对于节点C,更新C-B的距离为3;
- 对于节点D,到其他节点(包含自身)的距离保持不变。
Step2:考虑节点B;
Step3:考虑节点C;
Step4:考虑节点D
基于该示例总结节点 i 、 j i、j i、j之间距离更新的条件:
- 考虑节点 k k k时,若 d [ i ] [ k ] + d [ k ] [ j ] < d [ i ] [ j ] d[i][k]+d[k][j]<d[i][j] d[i][k]+d[k][j]<d[i][j],则更新节点 i 、 j i、j i、j之间的距离;
- 即 d [ i ] [ j ] = m i n ( d [ i ] [ j ] , d [ i ] [ k ] + d [ k ] [ j ] ) d[i][j]=min(d[i][j],d[i][k]+d[k][j]) d[i][j]=min(d[i][j],d[i][k]+d[k][j]),该公式为求解最短路的递推公式,证明过程见2.1
二、算法核心
2.1、递推公式推导
即需证明:考虑 N N N个节点之后(假设第 N N N个节点为节点 n n n),节点 i 、 j i、j i、j之间的最短路就一定已知
【已知结论(结论1):对于包含 N N N个节点的无负环图,节点 i 、 j i、j i、j之间一定存在不包含重复节点的最短路径】
【结论1】证明:
- 若节点 i 、 j i、j i、j之间存在包含环的路径( i . . . k . . . k . . . j i...k...k...j i...k...k...j),因为图中没有负环(节点 k . . . k k...k k...k的长度 ≥ 0 \geq0 ≥0),则不包含环的路径长度更短;
- 因为不包含环(无重复节点)的路径数量有限,其中的最短路径就是图中节点 i 、 j i、j i、j之间的最短路径。
基于**【结论1】**进行证明:
情况1:考虑前 N − 1 N-1 N−1个节点时,节点 i 、 j i、j i、j之间已经找到一条最短路,计为 d [ N − 1 ] [ i ] [ j ] d[N-1][i][j] d[N−1][i][j]
通过比较 d [ N − 1 ] [ i ] [ j ] d[N-1][i][j] d[N−1][i][j]与 d [ N − 1 ] [ i ] [ n ] + d [ N − 1 ] [ n ] [ j ] d[N-1][i][n]+d[N-1][n][j] d[N−1][i][n]+d[N−1][n][j]的值,可以确定节点 i 、 j i、j i、j之间的最短路 d [ N ] [ i ] [ j ] d[N][i][j] d[N][i][j]
说明:
-
其一:因为图中无负环,因此在考虑第 N N N节点 n n n之后,节点 i 、 n i、n i、n之间的最短路保持不变,节点 n 、 j n、j n、j之间的最短路保持不变(【结论1】),即 d [ N ] [ i ] [ n ] = d [ N − 1 ] [ i ] [ n ] , d [ N ] [ n ] [ j ] = d [ N − 1 ] [ n ] [ j ] d[N][i][n]=d[N-1][i][n],d[N][n][j]=d[N-1][n][j] d[N][i][n]=d[N−1][i][n],d[N][n][j]=d[N−1][n][j];
-
其二:因为 d [ N − 1 ] [ i ] [ j ] d[N-1][i][j] d[N−1][i][j]是节点 i 、 j i、j i、j之间不考虑节点 n n n的最短路,在考虑节点 n n n之后,节点 i 、 j i、j i、j之间新增了部分路径,这些路径都会经过节点 n n n,这些路径中的最短路为 d [ N − 1 ] [ i ] [ n ] + d [ N − 1 ] [ n ] [ j ] d[N-1][i][n]+d[N-1][n][j] d[N−1][i][n]+d[N−1][n][j];
-
因此只需要比较 d [ N − 1 ] [ i ] [ j ] d[N-1][i][j] d[N−1][i][j]与 d [ N − 1 ] [ i ] [ n ] + d [ N − 1 ] [ n ] [ j ] d[N-1][i][n]+d[N-1][n][j] d[N−1][i][n]+d[N−1][n][j]即可确定节点 i 、 j i、j i、j之间的最短路
情况2:考虑前 N − 1 N-1 N−1个节点时,节点 i 、 j i、j i、j之间尚未找到最短路
此种情况说明节点 i i i到节点 j j j的所有路径都经过第 N N N个节点,因此考虑第 N N N个节点之后,节点 i 、 j i、j i、j之间的最短路就已知,为 d [ N − 1 ] [ i ] [ n ] + d [ N − 1 ] [ n ] [ j ] d[N-1][i][n]+d[N-1][n][j] d[N−1][i][n]+d[N−1][n][j]
综合情况1、情况2,总结递推公式:
考虑第 N N N个节点 n n n时,节点 i 、 j i、j i、j之间的最短路一定存在,且为 d [ N ] [ i ] [ j ] = m i n ( d [ N − 1 ] [ i ] [ j ] , d [ N − 1 ] [ i ] [ n ] + d [ N − 1 ] [ n ] [ j ] ) d[N][i][j]=min(d[N-1][i][j],d[N-1][i][n]+d[N-1][n][j]) d[N][i][j]=min(d[N−1][i][j],d[N−1][i][n]+d[N−1][n][j]);
节点 i 、 j i、j i、j之间最短路的递推公式 d [ K ] [ i ] [ j ] = m i n ( d [ K − 1 ] [ i ] [ j ] , d [ K − 1 ] [ i ] [ k ] + d [ K − 1 ] [ k ] [ j ] ) d[K][i][j]=min(d[K-1][i][j],d[K-1][i][k]+d[K-1][k][j]) d[K][i][j]=min(d[K−1][i][j],d[K−1][i][k]+d[K−1][k][j])
其中假设第 K K K个节点的编号为 k k k, d [ K − 1 ] [ i ] [ k ] d[K-1][i][k] d[K−1][i][k]表示考虑前 K − 1 K-1 K−1个节点时节点 i i i与节点 k k k之间的最短路
K
K
K为0时,可以由节点之间的距离矩阵确定
d
[
0
]
[
i
]
[
j
]
d[0][i][j]
d[0][i][j]
d
[
0
]
[
i
]
[
j
]
=
{
0
i
=
j
l
i
,
j
相邻
(
l
为路段长度
)
+
∞
i
,
j
不相邻
(
编程时可以用足够大的正数表示
)
\begin{equation} d[0][i][j]=\left\{ \begin{aligned} 0 & \quad i=j\\ l & \quad i,j相邻(l为路段长度)\\ +\infty & \quad i,j不相邻(编程时可以用足够大的正数表示) \end{aligned} \right . \end{equation}
d[0][i][j]=⎩
⎨
⎧0l+∞i=ji,j相邻(l为路段长度)i,j不相邻(编程时可以用足够大的正数表示)
2.2、Floyd算法代码
算法核心代码为三层for循环:第一层循环为 k k k值的更新,后两层循环是图中 i 、 j i、j i、j节点之间最短距离的更新
for k in range(self.node_num):
for i in range(self.node_num):
for j in range(self.node_num):
# 判断是否要更新节点i、j之间的最短距离,shortest_path_matrix为距离矩阵
shortest_path_matrix[i, j] = min(shortest_path_matrix[i, j], shortest_path_matrix[i, k] + shortest_path_matrix[k, j])
完整代码如下:
class ShortestPathRecognition(object):
def __init__(self, adjacent_matrix, node_num, path_maximum):
self.adjacent_matrix = adjacent_matrix
self.node_num = node_num
self.path_maximum = path_maximum
def floyd(self):
"""
多源最短路求解算法
:return: 最短路径长度矩阵shortest_path_matrix,shortest_path_matrix[i, j]表示节点i、j之间的最短路径长度
"""
# 处理邻接矩阵,设置不相邻的节点之间路径长度(根据实际问题设置足够大的数:path_maximum)
for i in range(self.node_num):
for j in range(i + 1, self.node_num):
if self.adjacent_matrix[i, j] == 0:
self.adjacent_matrix[i, j] = self.adjacent_matrix[j, i] = self.path_maximum
# print(self.adjacent_matrix)
shortest_path_matrix = copy.deepcopy(self.adjacent_matrix)
for k in range(self.node_num):
for i in range(self.node_num):
for j in range(self.node_num):
shortest_path_matrix[i, j] = min(shortest_path_matrix[i, j], shortest_path_matrix[i, k] + shortest_path_matrix[k, j])
return shortest_path_matrix
2.3、其他
Floyd算法的一些细节(讨论较多但解释不清晰的地方)
2.3.1、递推公式距离更新细节分析
考虑前 K K K个节点时,根据 k 、 i 、 j k、i、j k、i、j的“位置关系”,更新节点 i 、 j i、j i、j的距离时有如下四种情况:
- d [ K ] [ i ] [ j ] = m i n ( d [ K − 1 ] [ i ] [ j ] , d [ K − 1 ] [ i ] [ k ] + d [ K − 1 ] [ k ] [ j ] ) d[K][i][j]=min(d[K-1][i][j],d[K-1][i][k]+d[K-1][k][j]) d[K][i][j]=min(d[K−1][i][j],d[K−1][i][k]+d[K−1][k][j]);
- d [ K ] [ i ] [ j ] = m i n ( d [ K − 1 ] [ i ] [ j ] , d [ K ] [ i ] [ k ] + d [ K − 1 ] [ k ] [ j ] ) d[K][i][j]=min(d[K-1][i][j],d[K][i][k]+d[K-1][k][j]) d[K][i][j]=min(d[K−1][i][j],d[K][i][k]+d[K−1][k][j]);
- d [ K ] [ i ] [ j ] = m i n ( d [ K − 1 ] [ i ] [ j ] , d [ K − 1 ] [ i ] [ k ] + d [ K ] [ k ] [ j ] ) d[K][i][j]=min(d[K-1][i][j],d[K-1][i][k]+d[K][k][j]) d[K][i][j]=min(d[K−1][i][j],d[K−1][i][k]+d[K][k][j]);
- d [ K ] [ i ] [ j ] = m i n ( d [ K − 1 ] [ i ] [ j ] , d [ K ] [ i ] [ k ] + d [ K ] [ k ] [ j ] ) d[K][i][j]=min(d[K-1][i][j],d[K][i][k]+d[K][k][j]) d[K][i][j]=min(d[K−1][i][j],d[K][i][k]+d[K][k][j])
因为图中无负环,因此在考虑第 K K K个节点之后(假设第 K K K个节点的编号为 k k k),节点 i 、 k i、k i、k之间的最短路保持不变,节点 k 、 j k、j k、j之间的最短路保持不变(【结论1】),以上四种情况可统一表示为 d [ K ] [ i ] [ j ] = m i n ( d [ K − 1 ] [ i ] [ j ] , d [ K − 1 ] [ i ] [ k ] + d [ K − 1 ] [ k ] [ j ] d[K][i][j]=min(d[K-1][i][j],d[K-1][i][k]+d[K-1][k][j] d[K][i][j]=min(d[K−1][i][j],d[K−1][i][k]+d[K−1][k][j]
2.3.2、三维降至两维:距离矩阵更新方式
理论上应该对每一个 K K K使用一个二维矩阵记录节点 i 、 j i、j i、j之间的最短距离(左图)
但是因为 K K K只跟 K − 1 K-1 K−1相关,所以可以使用一个二维矩阵记录节点 i 、 j i、j i、j之间的最短距离( K K K为0时,为初始的距离矩阵),仅需持续更新这个距离矩阵(右图)
- 要求
k
k
k在外层,
i
、
j
i、j
i、j在内层,详见2.2代码部分、2.3.3说明
2.3.3、 k k k在最外层的原因
核心原因:Floyd算法的核心是动态规划, k k k为动态规划的“阶段”,因此在最外层。
当 i 或 j i或j i或j在最外层时,不能保证考虑所有节点之后,距离矩阵中的值是节点之间的最短距离(详见2.2代码实现部分:三层for循环)
例如: i i i在最外层, k k k在最内层,对于节点 i = 2 、 j = 1 i=2、j=1 i=2、j=1,考虑节点 k = 3 k=3 k=3时,需要根据公式 d [ 2 ] [ 1 ] = m i n ( d [ 2 ] [ 1 ] , d [ 2 ] [ 3 ] + d [ 3 ] [ 1 ] ) d[2][1]=min(d[2][1],d[2][3]+d[3][1]) d[2][1]=min(d[2][1],d[2][3]+d[3][1])确定最短距离,但问题在于此时 d [ 2 ] [ 3 ] 、 d [ 3 ] [ 1 ] d[2][3]、d[3][1] d[2][3]、d[3][1]都还未被更新,据此计算出的 d [ 2 ] [ 1 ] d[2][1] d[2][1]也不能保证是节点 i = 2 、 j = 1 i=2、j=1 i=2、j=1之间的最短距离
三、相关链接
Floyd算法介绍: http://t.csdnimg.cn/xupz2
Floyed(弗洛伊德)最短路算法的证明和实现 - 白阳的文章 - 知乎https://zhuanlan.zhihu.com/p/106909361
Floyd算法的证明 - ChickenCC的文章 - 知乎https://zhuanlan.zhihu.com/p/613716744
Floyd算法为什么把k放在最外层? - m00nlight的回答 - 知乎https://www.zhihu.com/question/30955032/answer/68834307
Floyd算法为什么把k放在最外层? - 赵轩昂的回答 - 知乎https://www.zhihu.com/question/30955032/answer/50079000