更多「图论」相关证明文章见如下帖子:
图论相关证明文章列表
Floyd-Warshall算法(弗洛伊德算法): 求解图中任意两点的最短路径的算法。图可以是有向图或无向图,可以有负权边,但不能有负圈 (负圈上的顶点无最短路径,可无限短)。算法以 3 重循环考察任意顶点 到任意顶点 是否有经过任意顶点 的可松弛路径,即对每一个顶点 (外层循环),考察是否有 ,若有则更新 。
可以这样理解其工作过程。已知确定的任意两点 间有确定的最短路径 (只要无负圈,必有最短路径), 由多次松弛操作得到。先假设算法是正确的 (详细证明见后述),那么在 的最后一次松弛后 (通过顶点 松弛),得到 。同理, 和 是 的两个部分,它们由之前的松弛操作得到。例如松弛顶点 后得到 ,可知 ,松弛顶点 后得到 ,可知 。路径 的构建过程可以一直拆分溯源到某三个相邻顶点的连接。例如在 上有三个相邻顶点为 ,那么在外层循环处理 ,中层循环处理 ,内层循环处理 时,松弛操作将 「连接起来」(因为最短路径上的任意子路径都是最短的,必松弛)。顺着算法执行过程不难看出,算法通过外循环的 来连接边,通过不断连接短路径产生长路径,最终增长为完整的最短路径。
d(i, k) + d(k, j) < d(i, j) ,则 d(i, j) = d(i, k) + d(k, j) ,若有路径信息矩阵,对应更新。递归输出路径:通过路径信息的矩阵。 的值是 ,即 经过 到 ,在每次松弛时更新 。例如有最短路径 。程序结束后得到的路径信息矩阵如下。
| 顶点 | |||||
|---|---|---|---|---|---|
利用该矩阵,通过递归即可找到 。递归过程大致如下,顶点输出顺序即为路径顺序。
i > j, 找到b
i > b,找到a
i > a为null,输出i
a > b为null,输出a
b > j,找到c
b > c为null,输出b
c > j为null,输出c
最后输出j该算法的 本质是动态规划 ,以状态转移方程的形式描述如下,其中 表示 经过前 个顶点的松弛,得到的顶点 到顶点 的最短路径长度 。注意第一维的 表示 个顶点,第二维和第三维表示具体的顶点。
1. 定义: dp[k][i][j] 表示经过前 k 个顶点的松弛,得到的顶点 i 到顶点 j 的最短路径长度。
2. 边界: dp[0][i][j] = Infinity
3. 递推: dp[k][i][j] = min{dp[k-1][i][j], dp[k-1][i][k] + dp[k-1][k][j]}最短路径 不经过 第 个顶点 (顶点 ):
最短路径 经过 第 个顶点 (顶点 ):
// floyd核心伪代码
for(k : V)
for(i : V)
for(j : V)
if(d(i, k) + d(k, j) < d(i, j))
d(i, j) = d(i, k) + d(k, j)补充说明:已知点 之间的最短路径为 ,那么 上的任意两点 的最短路径确定在 上。反证法简单可证。假设 上两点 之间的最短路径经过一不在 上的顶点 ,那 的最短路径也就不是 ,而是 。
下面通过一个例子观察 Floyd 算法的动态规划过程,对其正确性可以有更直观的感受。由于算法总是在整张图上进行处理,展示整张图将使过程变得杂乱,因此我们只选取一条 到 的最短路径,展现该路径的构建过程。由于这条路径是随意选取的,其他所有在图上的 任意两点 的路径的构成都是类似的。
设图 中 间最短路径 为 。最外层循环对该路径上顶点的处理顺序可以是任意顺序,例如 (分别标上序号 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,表示外层循环处理的先后顺序)。现在我们以溯源的方式从处理最后一个顶点 得到 开始观察,且只观察程序对上述十个顶点的处理(对其他顶点的处理形成其他路径)。该路径的取得只与三个循环对该路径上的顶点的操作有关,循环对其他顶点的操作不影响结果,因为其他顶点不在该最短路径上,由它们导致的松弛不影响路径 ,或者说 会从若干条 到 的路径中胜出。
外层循环处理 之后由 的结果得到 到 的最短路径长 ,因此 和 此时必是已知的。继续溯源,看看 和 是如何得到的。到 的路径上 是最后被处理的,处理 时计算 ,其中 是边长,这是程序开始时已知, 需要继续溯源, ,其中 是边长, , 和 是边长。其余过程如图,标红处表示相邻的顶点的边长,在程序开始时得到。
可以看出,外层循环处理 i 到 j 的最短路径的所有顶点的过程中,先处理的顶点得到 子路径总能够为后处理的顶点构建更长的子路径 ,直到处理 (最短路径上的) 最后一个顶点时,将两个子路径连接起来形成最终的最短路径,这正是动态规划过程的体现。
不同于 BF 动态规划过程的单串单依赖,Floyd 动态规划是 多依赖 的。也可以描述为某一次的松弛形成的路径 不一定直接作用于下一次松弛 ,而是在之后某一次松弛中发生作用。例如处理 和处理 是外层循环的两次相邻的操作,它们分别产生了两条不相连的子路径 和 。之后外层循环处理 时 增长为 ,处理 时增长为 。 在外层循环处理 时增长为 ,处理 时增长为 ( 在外层循环处理 时得到)。最终在处理 时得到 。


总结 Floyd 算法的过程,外层循环执行完第 次,给出由 条边组成的路径,下一次会利用长度为 的路径连接出长度为 的路径。这就好像将任意两点连成单边线(只要这两点之间存在路径),然后再将两条单边线作为零件连成长度为 2 的线,因为具备所有单边线,所以无论长度为 2 的线是由哪些单边线组成的,都可以找到并连起来。然后再利用长度为 1,2 的线作为零件连成长度为 3 的线,因为无论一条长度为 3 的线是如何构成的,构成它的单边线和 2边线都已具备。以此类推直到连出所有可能长度的线。
时间复杂度: 3 重循环, 。
空间复杂度: 用于存储路径长度信息的二维矩阵 (若需求具体路径,还需路径信息矩阵),为 。