Floyd算法原理及公式推导

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 ij之间距离更新的条件:

  • 考虑节点 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 ij之间的距离;
  • 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 ij之间的最短路就一定已知

【已知结论(结论1):对于包含 N N N个节点的无负环图,节点 i 、 j i、j ij之间一定存在不包含重复节点的最短路径】

【结论1】证明:

  • 若节点 i 、 j i、j ij之间存在包含环的路径( 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 ij之间的最短路径。

基于**【结论1】**进行证明:

情况1:考虑前 N − 1 N-1 N1个节点时,节点 i 、 j i、j ij之间已经找到一条最短路,计为 d [ N − 1 ] [ i ] [ j ] d[N-1][i][j] d[N1][i][j]

通过比较 d [ N − 1 ] [ i ] [ j ] d[N-1][i][j] d[N1][i][j] d [ N − 1 ] [ i ] [ n ] + d [ N − 1 ] [ n ] [ j ] d[N-1][i][n]+d[N-1][n][j] d[N1][i][n]+d[N1][n][j]的值,可以确定节点 i 、 j i、j ij之间的最短路 d [ N ] [ i ] [ j ] d[N][i][j] d[N][i][j]

说明:

  • 其一:因为图中无负环,因此在考虑第 N N N节点 n n n之后,节点 i 、 n i、n in之间的最短路保持不变,节点 n 、 j n、j nj之间的最短路保持不变(【结论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[N1][i][n],d[N][n][j]=d[N1][n][j]

  • 其二:因为 d [ N − 1 ] [ i ] [ j ] d[N-1][i][j] d[N1][i][j]是节点 i 、 j i、j ij之间不考虑节点 n n n的最短路,在考虑节点 n n n之后,节点 i 、 j i、j ij之间新增了部分路径,这些路径都会经过节点 n n n,这些路径中的最短路为 d [ N − 1 ] [ i ] [ n ] + d [ N − 1 ] [ n ] [ j ] d[N-1][i][n]+d[N-1][n][j] d[N1][i][n]+d[N1][n][j]

  • 因此只需要比较 d [ N − 1 ] [ i ] [ j ] d[N-1][i][j] d[N1][i][j] d [ N − 1 ] [ i ] [ n ] + d [ N − 1 ] [ n ] [ j ] d[N-1][i][n]+d[N-1][n][j] d[N1][i][n]+d[N1][n][j]即可确定节点 i 、 j i、j ij之间的最短路

情况2:考虑前 N − 1 N-1 N1个节点时,节点 i 、 j i、j ij之间尚未找到最短路

此种情况说明节点 i i i到节点 j j j的所有路径都经过第 N N N个节点,因此考虑第 N N N个节点之后,节点 i 、 j i、j ij之间的最短路就已知,为 d [ N − 1 ] [ i ] [ n ] + d [ N − 1 ] [ n ] [ j ] d[N-1][i][n]+d[N-1][n][j] d[N1][i][n]+d[N1][n][j]

综合情况1、情况2,总结递推公式:

考虑第 N N N个节点 n n n时,节点 i 、 j i、j ij之间的最短路一定存在,且为 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[N1][i][j],d[N1][i][n]+d[N1][n][j])

节点 i 、 j i、j ij之间最短路的递推公式 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[K1][i][j],d[K1][i][k]+d[K1][k][j])

其中假设第 K K K个节点的编号为 k k k d [ K − 1 ] [ i ] [ k ] d[K-1][i][k] d[K1][i][k]表示考虑前 K − 1 K-1 K1个节点时节点 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 ij节点之间最短距离的更新

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 kij的“位置关系”,更新节点 i 、 j i、j ij的距离时有如下四种情况:

  • 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[K1][i][j],d[K1][i][k]+d[K1][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[K1][i][j],d[K][i][k]+d[K1][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[K1][i][j],d[K1][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[K1][i][j],d[K][i][k]+d[K][k][j])

因为图中无负环,因此在考虑第 K K K个节点之后(假设第 K K K个节点的编号为 k k k),节点 i 、 k i、k ik之间的最短路保持不变,节点 k 、 j k、j kj之间的最短路保持不变(【结论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[K1][i][j],d[K1][i][k]+d[K1][k][j]

2.3.2、三维降至两维:距离矩阵更新方式

理论上应该对每一个 K K K使用一个二维矩阵记录节点 i 、 j i、j ij之间的最短距离(左图)

但是因为 K K K只跟 K − 1 K-1 K1相关,所以可以使用一个二维矩阵记录节点 i 、 j i、j ij之间的最短距离( K K K为0时,为初始的距离矩阵),仅需持续更新这个距离矩阵(右图)

  • 要求 k k k在外层, i 、 j i、j ij在内层,详见2.2代码部分、2.3.3说明
    在这里插入图片描述
2.3.3、 k k k在最外层的原因

核心原因:Floyd算法的核心是动态规划, k k k为动态规划的“阶段”,因此在最外层。

i 或 j i或j ij在最外层时,不能保证考虑所有节点之后,距离矩阵中的值是节点之间的最短距离(详见2.2代码实现部分:三层for循环)

例如: i i i在最外层, k k k在最内层,对于节点 i = 2 、 j = 1 i=2、j=1 i=2j=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=2j=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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值