Floyd-Warshall算法基于动态规划思想,通过逐步引入中间节点更新所有节点对之间的最短路径,其核心的三层循环顺序和状态设计是保证算法正确性的关键,很多初学者容易在循环顺序调整上踩坑,也可以通过合理的优化提升算法的实用价值。

算法核心状态定义
我们首先明确Floyd-Warshall算法的状态定义:设dp[k][i][j]表示只允许经过节点编号1到k作为中间节点时,从节点i到节点j的最短路径长度。初始状态下,dp[0][i][j]就是i到j的直接边权,如果不存在直接边则设为无穷大,i到i的距离设为0。
状态转移方程为:dp[k][i][j] = min(dp[k-1][i][j], dp[k-1][i][k] + dp[k-1][k][j]),含义是要么不经过k这个中间节点,要么经过k,取两种情况的较小值。
循环顺序的设计逻辑
标准的三层循环顺序是先遍历中间节点k,再遍历起点i,最后遍历终点j,对应代码如下:
def floyd_standard(graph, n):
# graph是邻接矩阵,n是节点数量
dp = [row[:] for row in graph] # 初始化dp[0]状态
for k in range(n): # 第一层:中间节点
for i in range(n): # 第二层:起点
for j in range(n): # 第三层:终点
if dp[i][k] + dp[k][j] < dp[i][j]:
dp[i][j] = dp[i][k] + dp[k][j]
return dp循环顺序不能调换的核心原因是状态依赖:dp[k][i][j]依赖的是dp[k-1]阶段的所有状态,也就是经过前k-1个中间节点的结果。如果先遍历i和j,再遍历k,那么在更新dp[i][j]时,可能会用到已经更新过的dp[i][k]或者dp[k][j],相当于允许了经过超过k的中间节点,破坏了动态规划的阶段正确性,最终导致结果错误。
错误顺序示例
我们把循环顺序调整为先遍历i和j,再遍历k,看看输出结果的变化:
def floyd_wrong(graph, n):
dp = [row[:] for row in graph]
for i in range(n):
for j in range(n):
for k in range(n): # 错误:最后遍历中间节点k
if dp[i][k] + dp[k][j] < dp[i][j]:
dp[i][j] = dp[i][k] + dp[k][j]
return dp
# 测试用例:4个节点,边为0-1(2),1-2(3),0-3(10),3-2(1)
INF = float('inf')
test_graph = [
[0, 2, INF, 10],
[INF, 0, 3, INF],
[INF, INF, 0, INF],
[INF, INF, 1, 0]
]
n = 4
print("标准顺序结果:")
res1 = floyd_standard(test_graph, n)
for row in res1:
print(row)
print("错误顺序结果:")
res2 = floyd_wrong(test_graph, n)
for row in res2:
print(row)运行后可以发现,错误顺序计算出的0到2的最短路径是10+1=11,而正确结果应该是0-1-2的5,就是因为循环顺序错误导致状态更新混乱。
状态优化可行方案
1. 滚动数组优化空间
观察状态转移方程,dp[k]阶段的状态只依赖dp[k-1]阶段,因此可以不用三维数组,直接用二维数组覆盖更新,因为每次用dp[i][k]和dp[k][j]更新时,只要k是在最外层循环,就不会用到已经被覆盖的k阶段之前的状态,空间复杂度从O(n^3)降为O(n^2),代码如下:
def floyd_space_optimize(graph, n):
dp = [row[:] for row in graph]
for k in range(n):
for i in range(n):
for j in range(n):
if dp[i][k] + dp[k][j] < dp[i][j]:
dp[i][j] = dp[i][k] + dp[k][j]
return dp这里要注意,即使做了空间优化,循环顺序依然不能调换,否则还是会出现状态错误。
2. 路径记录优化
如果需要输出具体的最短路径,可以增加一个path数组记录每个节点对的最短路径的前驱节点,初始化时如果有直接边则path[i][j] = i,否则为-1,更新最短路径时同步更新前驱节点:
def floyd_with_path(graph, n):
dp = [row[:] for row in graph]
path = [[-1 for _ in range(n)] for _ in range(n)]
# 初始化路径数组
for i in range(n):
for j in range(n):
if i != j and dp[i][j] != INF:
path[i][j] = i
# 核心循环
for k in range(n):
for i in range(n):
for j in range(n):
if dp[i][k] + dp[k][j] < dp[i][j]:
dp[i][j] = dp[i][k] + dp[k][j]
path[i][j] = path[k][j] # 继承k到j的前驱
return dp, path
# 回溯输出路径的函数
def get_path(path, i, j):
if path[i][j] == -1:
return []
res = [j]
while i != j:
j = path[i][j]
res.append(j)
return res[::-1]3. 负权边处理优化
Floyd-Warshall算法可以处理负权边,但无法处理带有负权环的情况,可以在算法结束后检查所有dp[i][i]是否有小于0的值,如果有则说明存在负权环,给出对应提示即可,不需要额外增加复杂逻辑。
总结
Floyd-Warshall算法的三层循环顺序必须保持中间节点k在最外层,这是由动态规划的阶段依赖关系决定的,调换顺序会破坏状态更新的正确性。在空间优化、路径记录等场景下,只要不调整核心的循环顺序,就可以安全地做对应优化,在解决多源最短路径问题时能发挥更好的效果。
Floyd-Warshall动态规划最短路径循环顺序状态优化修改时间:2026-06-04 03:27:09