本文如有不妥之处,敬请读者在评论区指正,作者将及时修正。
弗洛伊德算法的正确性证明
弗洛伊德算法(Floyd algorithm)是一种求解图中所有点对之间最短路径的动态规划算法。它的基本思想是,对于图中的任意两个顶点 i 和 j,如果存在一个顶点 k,使得经过 k 顶点的路径比直接从 i 到 j 的路径更短,那么就更新 i 到 j 的最短路径为 i 到 k 加上 k 到 j 的路径长度。算法的时间复杂度为 O(n^3),其中 n 是图中的顶点数。
弗洛伊德算法的正确性可以用数学归纳法或者动态规划的思想来证明。下面分别介绍这两种证明方法。
数学归纳法
数学归纳法的证明需要用到以下符号和定义:
- 用 V 表示图中的顶点集合,假设 V = {1, 2, …, n}。
- 用 w(i, j) 表示从 i 到 j 的边的权重,如果不存在这样的边,就设为无穷大。
- 用 d(i, j) 表示从 i 到 j 的最短路径长度,如果不存在这样的路径,就设为无穷大。
- 用 d_k(i, j) 表示从 i 到 j 的最短路径长度,且该路径中所有经过的顶点都属于 {1, 2, …, k},如果不存在这样的路径,就设为无穷大。
弗洛伊德算法的核心是以下递推公式:
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)\} dk(i,j)=min{dk−1(i,j),dk−1(i,k)+dk−1(k,j)}
这个公式的含义是,对于任意两个顶点 i 和 j,如果允许经过 k 顶点,那么从 i 到 j 的最短路径要么就是不经过 k 的最短路径,要么就是经过 k 的最短路径,即从 i 到 k 的最短路径加上从 k 到 j 的最短路径。
要证明这个公式成立,可以用数学归纳法。首先,当 k = 0 时,显然有:
d 0 ( i , j ) = w ( i , j ) d_0(i, j) = w(i, j) d0(i,j)=w(i,j)
这是因为当不允许经过任何顶点时,从 i 到 j 的最短路径就是直接连接它们的边(如果存在)。
其次,假设当 k = m 时,公式成立,即:
d m ( i , j ) = min { d m − 1 ( i , j ) , d m − 1 ( i , m ) + d m − 1 ( m , j ) } d_m(i, j) = \min\{d_{m-1}(i, j), d_{m-1}(i, m) + d_{m-1}(m, j)\} dm(i,j)=min{dm−1(i,j),dm−1(i,m)+dm−1(m,j)}
那么当 k = m + 1 时,有:
d m + 1 ( i , j ) = min { d m ( i , j ) , d m ( i , m + 1 ) + d m ( m + 1 , j ) } = min { min { d m − 1 ( i , j ) , d m − 1 ( i , m ) + d m − 1 ( m , j ) } , min { d m − 1 ( i , m + 1 ) , d m − 1 ( i , m ) + d m − 1 ( m , m + 1 ) } + min { d m − 1 ( m + 1 , j ) , d m − 1 ( m + 1 , m ) + d m − 1 ( m , j ) } } = min { d m − 1 ( i , j ) , d m − 1 ( i , m ) + d m − 1 ( m , j ) , d m − 1 ( i , m + 1 ) + d m − 1 ( m + 1 , j ) , d m − 1 ( i , m ) + d m − 1 ( m , m + 1 ) + d m − 1 ( m + 1 , m ) + d m − 1 ( m , j ) } = min { d m − 1 ( i , j ) , d m − 1 ( i , m + 1 ) + d m − 1 ( m + 1 , j ) } \begin{aligned} d_{m+1}(i, j) &= \min\{d_m(i, j), d_m(i, m+1) + d_m(m+1, j)\} \\ &= \min\{\min\{d_{m-1}(i, j), d_{m-1}(i, m) + d_{m-1}(m, j)\}, \\ &\quad \min\{d_{m-1}(i, m+1), d_{m-1}(i, m) + d_{m-1}(m, m+1)\} \\ &\quad + \min\{d_{m-1}(m+1, j), d_{m-1}(m+1, m) + d_{m-1}(m, j)\}\} \\ &= \min\{d_{m-1}(i, j), d_{m-1}(i, m) + d_{m-1}(m, j), \\ &\quad d_{m-1}(i, m+1) + d_{m-1}(m+1, j), \\ &\quad d_{m-1}(i, m) + d_{m-1}(m, m+1) + d_{m-1}(m+1, m) + d_{m-1}(m,j)\} \\ &= \min\{d_{m-1}(i,j), d_{m-1}(i,m+1)+d_{m-1}(m+1,j)\} \end{aligned} dm+1(i,j)=min{dm(i,j),dm(i,m+1)+dm(m+1,j)}=min{min{dm−1(i,j),dm−1(i,m)+dm−1(m,j)},min{dm−1(i,m+1),dm−1(i,m)+dm−1(m,m+1)}+min{dm−1(m+1,j),dm−1(m+1,m)+dm−1(m,j)}}=min{dm−1(i,j),dm−1(i,m)+dm−1(m,j),dm−1(i,m+1)+dm−1(m+1,j),dm−1(i,m)+dm−1(m,m+1)+dm−1(m+1,m)+dm−1(m,j)}=min{dm−1(i,j),dm−1(i,m+1)+dm−1(m+1,j)}
这里用到了三个事实:
- d m ( i , j ) = min { d m ( i , j ) , d m ( i , m ) + d m ( m , j ) } d_m(i,j)=\min\{d_m(i,j),d_m(i,m)+d_m(m,j)\} dm(i,j)=min{dm(i,j),dm(i,m)+dm(m,j)}(根据归纳假设)
- d m ( i , m ) = d m ( m , i ) d_m(i,m)=d_m(m,i) dm(i,m)=dm(m,i)(根据对称性)
- d m ( m , m ) = 0 d_m(m,m)=0 dm(m,m)=0(根据自反性)
由此可见,当 k = m + 1 时,公式仍然成立。因此,根据数学归纳法,公式对于所有的 k 都成立。
最后,当 k = n 时,我们就得到了从 i 到 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)\} dn(i,j)=min{dn−1(i,j),dn−1(i,n)+dn−1(n,j)}
这就完成了弗洛伊德算法的正确性证明。
动态规划的思想
动态规划的思想是将一个复杂的问题分解为若干个子问题,并将子问题的解存储起来,以避免重复计算。弗洛伊德算法也可以看作是一种动态规划算法。它的子问题是求解从 i 到 j 的最短路径长度,并且限制了经过的顶点集合。它的状态转移方程就是上面提到的递推公式。
要证明弗洛伊德算法的正确性,我们可以用动态规划的思想来分析。首先,我们可以初始化一个二维数组 D 来存储子问题的解。D[i][j] 表示从 i 到 j 的最短路径长度。初始化时,我们将 D[i][j] 设为 w(i,j),即直接连接两个顶点的边的权重(如果存在)。这相当于只考虑了不经过任何顶点的情况。
然后,我们可以按照顶点编号从小到大依次考虑每个顶点作为中间顶点。对于每个顶点 k,在 D 中更新所有可能经过它的两个顶点之间的最短路径长度。具体地说,就是对于每一对 i 和 j(不等于 k),检查是否有:
D [ i ] [ j ] > D [ i ] [ k ] + D [ k ] [ j ] D[i][j] > D[i][k] + D[k][j] D[i][j]>D[i][k]+D[k][j]
如果成立,则说明经过 k 顶点可以使得从 i 到 j 的路径更短,则更新 D[i][j] 为 D[i][k] + D[k][j]。否则保持 D[i][j] 不变。
这个过程可以保证每次更新后的 D[i][j] 都是从 i 到 j 的最短路径长度,并且只考虑了前 k 个顶点作为中间顶点。这是因为:
- 如果不经过前 k 个顶点就能找到从 i 到 j 的最短路径,则在更新前后 D[i][j] 都不会变化。
- 如果经过前 k 个顶点中的某些顶点能够找到从 i 到 j 的更短路径,则在更新时 D[i][j] 会被赋值为最小的那个路径长度。
- 如果经过第 k 个顶点能够找到从 i 到 j 的更短路径,则在更新时 D[i][j] 会被赋值为 D[i][k] + D[k][j],这也是最小的那个路径长度。
因此,当我们考虑完所有的顶点后,D[i][j] 就是从 i 到 j 的最短路径长度,而且不限制经过的顶点集合。这就证明了弗洛伊德算法的正确性。
如果我们还想知道从 i 到 j 的最短路径具体经过哪些顶点,我们可以用一个二维数组 P 来记录每次更新 D[i][j] 时的中间顶点 k。初始化时,我们将 P[i][j] 设为 -1,表示没有中间顶点。每次更新 D[i][j] 时,我们也更新 P[i][j] 为 k。这样,当算法结束后,我们就可以通过 P 数组来回溯从 i 到 j 的最短路径。具体地说,就是从 i 开始,依次查找 P[i][j] 的值,如果是 -1,则说明直接到达 j;如果是 k,则说明经过了 k 顶点,然后再查找 P[i][k] 和 P[k][j] 的值,直到找到 -1 为止。这样就得到了从 i 到 j 的最短路径上的所有顶点。
参考
(1) 数学归纳法
(2) 动态规划的思想来证明
(3) 证明过程-知乎