Floyd-Warshall算法求解所有顶点对最短路径
题目描述
给定一个带权有向图,图中可能包含负权边,但不存在负权回路。要求计算图中任意两个顶点之间的最短路径长度。也就是说,我们需要一个矩阵作为结果,其中第i行第j列的元素表示从顶点i到顶点j的最短路径的权值之和。
解题思路
我们将采用动态规划的方法来解决这个问题。核心思想是:逐步考虑将每个顶点作为中间顶点,来更新任意两点间的最短路径估计值。
-
状态定义
我们定义一个二维数组dist,其中dist[k][i][j]表示:在考虑前k个顶点(即顶点0, 1, ..., k-1)作为中间顶点的情况下,从顶点i到顶点j的最短路径长度。
然而,我们可以通过重用同一个二维数组来节省空间,将状态优化为dist[i][j],并在算法过程中不断更新它。此时,dist[i][j]表示当前状态下(即已经考虑过某些中间顶点后)的i到j的最短路径估计值。 -
初始状态(k=0)
在没有任何中间顶点可用时,从i到j的最短路径有两种情况:- 如果i等于j,那么路径长度为0。
- 如果i不等于j,那么最短路径就是连接i和j的边的权值。如果不存在直接的边,我们先用一个很大的数(比如无穷大)来表示不可达。
因此,我们初始化距离矩阵dist: dist[i][i] = 0(顶点到自身的距离为0)- 对于每条边 (u, v, w)(表示从u到v有一条权值为w的边),
dist[u][v] = w - 对于其他情况,
dist[i][j] = 无穷大
-
状态转移方程(核心步骤)
现在,我们开始逐个考虑将每个顶点k(从0到n-1)加入允许的中间顶点集合。
对于每一对顶点(i, j),我们检查是否存在一条路径,通过新加入的顶点k,能够比当前已知的路径更短。
具体来说,对于每一对(i, j),我们比较:- 不经过k的当前最短路径:
dist[i][j](即在上一步状态下的值) - 经过k的新路径:这条路径是从i到k,再从k到j。其长度为
dist[i][k] + dist[k][j](注意,这里的dist[i][k]和dist[k][j]是在当前步骤中已经更新过的,意味着它们本身可能已经利用了之前加入的中间顶点,从而保证了最优子结构)。
我们选择两者中较小的那个作为新的最短路径估计值。
状态转移方程为:
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
- 不经过k的当前最短路径:
-
算法步骤
基于以上思路,算法的具体步骤非常清晰:
a. 初始化距离矩阵dist,如上所述。
b. 循环k,从0到n-1(n为顶点总数):
对于每一个k,循环每一对顶点(i, j)(i和j都从0到n-1):
如果dist[i][k]和dist[k][j]都不是无穷大(即i到k和k到j是可达的),并且dist[i][k] + dist[k][j]小于当前的dist[i][j]:
那么更新dist[i][j] = dist[i][k] + dist[k][j]。
c. 循环结束后,dist矩阵中存储的就是所有顶点对之间的最短路径长度。 -
负权边的处理与负权回路检测
题目假设图中不存在负权回路。如果存在负权回路,则最短路径的概念会变得没有意义,因为可以无限次绕行该回路使得路径长度趋于负无穷。
虽然本算法不允许负权回路,但它可以处理负权边。算法结束后,我们可以通过检查dist矩阵的主对角线来辅助检测负权回路:如果存在某个顶点i,使得dist[i][i] < 0,则说明图中存在一个经过顶点i的负权回路。因为到自身的距离理应始终为0,出现负数意味着存在一条绕回自身的负权路径。
总结
Floyd-Warshall算法通过三层循环,以动态规划的方式系统地考虑了所有顶点作为中间点的可能性,最终计算出所有顶点对之间的最短路径。它的时间复杂度为O(n³),空间复杂度为O(n²)。虽然对于稀疏图来说效率不如运行n次Dijkstra算法,但其代码极其简洁,并且能直接处理带有负权边(无负权回路)的图,是其显著优势。