← 中文资源

简体中文版本

Floyd-Warshall 全源最短路径

用三重循环计算任意两点间的最短距离,并通过下一跳矩阵还原路径,时间复杂度为节点数的三次方。

阅读英文原文

功能说明

Floyd-Warshall 适合节点数不多的稠密图,一次计算就能得到所有点对之间的最短距离。算法依次把每个节点当作中转点,尝试用起点到中转点再到终点的距离更新起点到终点的距离。只要图中没有负权环,结果就是正确的;存在负权边也可以处理,这是它相对于 Dijkstra 的优势。

本例同时维护距离矩阵和下一跳矩阵。距离矩阵初始时对角线为零,直接边为权重,其余为无穷。更新距离成功时,把下一跳改为起点到中转点的下一跳,从而在结束后按矩阵逐跳还原完整路径。

关键代码

floyd_warshall 先收集排序后的节点,建立二维字典,避免依赖节点命名。无穷用 float 的 inf 表示,对角线初始化为零。读入边时写入距离和下一跳。三重循环外层是中转点,内层是起点和终点;若起点到中转点不可达就跳过,减少无效加法。候选距离更小时同时更新距离和下一跳。build_path 从起点开始,按下一跳前进直到终点;起点等于终点时直接返回单节点路径。

使用注意

时间复杂度是节点数三次方,空间是平方,节点数上千时会明显变慢,应改用逐源 Dijkstra 或 Johnson 算法。距离用浮点表示可能产生舍入误差,金额或精确权值应使用整数。该实现没有检测负权环;若某节点到自身的距离被更新为负数,说明存在负权环。图字典未列出的节点不会出现,隐式节点需要先补齐。路径还原前应确认下一跳矩阵已正确初始化。

验证方式

运行脚本,A 到 C 的最短距离为五,路径为 A、B、C;D 到 C 经过 A 与 B,距离为七;C 到自身距离为零,路径只含 C。修改某条边权后重跑,距离和路径应同步变化。可加入一个负权边但不构成环,验证算法仍能给出更短路径。

代码示例

INF = float("inf")


def floyd_warshall(
    graph: dict[str, dict[str, float]]
) -> tuple[dict[str, dict[str, float]], dict[str, dict[str, str | None]]]:
    nodes = sorted(graph)
    dist = {source: {target: INF for target in nodes} for source in nodes}
    nxt: dict[str, dict[str, str | None]] = {
        source: {target: None for target in nodes} for source in nodes
    }
    for node in nodes:
        dist[node][node] = 0.0
    for source, edges in graph.items():
        for target, weight in edges.items():
            dist[source][target] = weight
            nxt[source][target] = target

    for middle in nodes:
        for source in nodes:
            if dist[source][middle] == INF:
                continue
            for target in nodes:
                candidate = dist[source][middle] + dist[middle][target]
                if candidate < dist[source][target]:
                    dist[source][target] = candidate
                    nxt[source][target] = nxt[source][middle]
    return dist, nxt


def build_path(
    nxt: dict[str, dict[str, str | None]], source: str, target: str
) -> list[str] | None:
    if source == target:
        return [source]
    if nxt[source][target] is None:
        return None
    route = [source]
    while source != target:
        step = nxt[source][target]
        if step is None:
            return None
        source = step
        route.append(source)
    return route


if __name__ == "__main__":
    graph = {
        "A": {"B": 3.0, "D": 7.0},
        "B": {"A": 8.0, "C": 2.0},
        "C": {"A": 5.0, "D": 1.0},
        "D": {"A": 2.0},
    }
    dist, nxt = floyd_warshall(graph)
    print("A to C:", dist["A"]["C"], build_path(nxt, "A", "C"))
    print("D to C:", dist["D"]["C"], build_path(nxt, "D", "C"))
    print("C to C:", dist["C"]["C"], build_path(nxt, "C", "C"))