相关笔记

概览

本节介绍一种基于矩阵乘法所有结点对最短路径(All-Pairs Shortest Paths, APSP)算法。核心思想是将最短路径问题转化为== 半环上的矩阵乘法,通过逐步扩展路径所允许的边数来逼近最短路径。由此衍生出两种算法:SLOW-ALL-PAIRS-SHORTEST-PATHS==()和 FASTER-ALL-PAIRS-SHORTEST-PATHS),后者利用重复平方技术加速收敛。

要点列表:

  • 定义 为从顶点 到顶点 的==最多包含 条边==的最短路径权重
  • EXTEND-SHORTEST-PATHS 过程执行一次 矩阵乘法,将路径边数上限从 扩展到
  • 慢速版本重复调用 EXTEND 次,复杂度为
  • 快速版本利用重复平方,仅需 次矩阵乘法,复杂度为
  • 该框架可进一步结合 Strassen 算法,将矩阵乘法的复杂度从 降低到

知识结构总览

flowchart TD
    A["23.1 最短路径与矩阵乘法"] --> B["问题定义"]
    A --> C["逐步扩展思想"]
    A --> D["EXTEND-SHORTEST-PATHS"]
    A --> E["两种算法"]
    A --> F["与矩阵乘法的关系"]

    B --> B1["APSP: 求所有顶点对的最短路径"]
    B --> B2["输入: 权重矩阵 W"]
    B --> B3["要求: w_ii = 0"]

    C --> C1["L^(m)_ij: 最多m条边的最短路径"]
    C --> C2["L^(1) = W"]
    C --> C3["L^(m) = L^(m-1) * W (min-plus)"]

    D --> D1["三重循环: i, j, k"]
    D --> D2["l'_ij = min(l_ij, l_ik + w_kj)"]
    D --> D3["等价于 (min,+) 矩阵乘法"]

    E --> E1["SLOW: 重复调用 |V|-1 次"]
    E --> E2["FAST: 重复平方 L^(1), L^(2), L^(4), ..."]
    E1 --> E1a["复杂度: O(V^4)"]
    E2 --> E2a["复杂度: O(V^3 lg V)"]

    F --> F1["(min,+) 半环 (Tropical Semiring)"]
    F --> F2["结合律保证正确性"]
    F --> F3["可应用 Strassen 优化"]

核心思想

核心思路

将所有结点对最短路径问题视为一种特殊的矩阵乘法。标准矩阵乘法使用 (加法, 乘法) 运算,而最短路径使用 (min, 加法) 运算。通过反复执行这种”扩展”操作,路径所允许的边数逐步增加,最终收敛到真正的最短路径。

1. APSP 问题定义

所有结点对最短路径问题(All-Pairs Shortest Paths)

输入: 一个带权有向图 ,权重函数 ,以权重矩阵 表示,其中

  • ,则 为边 的权重
  • ,则
  • 对所有 ,要求 (从顶点到自身的空路径权重为0)

输出: 一个 矩阵 ,其中 为从顶点 到顶点 最短路径权重

  • 若从 不存在路径,则
  • 若从 的路径上存在负权环,则

2. 逐步扩展性质

逐步扩展(Extending Shortest Paths)

定义 为一个 矩阵,其中 表示从顶点 到顶点 的==最多包含 条边==的最短路径权重。

关键递推关系:

展开写即为:

直觉理解: 一条从 的最多 条边的路径,可以看作一条从 到某个中间顶点 的最多 条边的路径,再加上最后一条边

为什么这个递推是正确的?

考虑从 的最多 条边的最短路径 。如果 条边,设 的倒数第二个顶点为 ,则 的前缀是从 的最多 条边的路径,权重为 ,加上最后一条边 的权重 。遍历所有可能的 取最小值即可。如果 少于 条边,则 已经记录了这条更短路径的权重,取 时自然会被选中。

3. EXTEND-SHORTEST-PATHS 伪代码

算法执行流程

  1. 对每对顶点 (i, j),将新矩阵对应位置初始化为 无穷大
  2. 遍历所有中间顶点 k
  3. 对每个 k,尝试经过 k 中转:比较当前值与 l.ik + w.kj,取较小者
  4. 返回新矩阵 L’
    A["创建新矩阵 L'"] --> B["对每对顶点 i, j 初始化为无穷大"]
    B --> C["遍历中间顶点 k = 1 到 n"]
    C --> D{"l.ik + w.kj < 当前值?"}
    D -- 是 --> E["更新 l'_ij = l.ik + w.kj"]
    D -- 否 --> F["保持 l'_ij 不变"]
    E --> G{"还有下一个 k?"}
    F --> G
    G -- 是 --> C
    G -- 否 --> H{"还有下一对 i, j?"}
    H -- 是 --> B
    H -- 否 --> I["返回 L'"]
EXTEND-SHORTEST-PATHS(L, W)
1  n = L.rows
2  let L' = (l'_{ij}) be a new n x n matrix
3  for i = 1 to n
4      for j = 1 to n
5          l'_{ij} = ∞
6          for k = 1 to n
7              l'_{ij} = min(l'_{ij}, l_{ik} + w_{kj})
8  return L'

EXTEND-SHORTEST-PATHS 的理解

三重循环的结构:外层遍历目标顶点对,内层遍历所有可能的中间顶点。对每对顶点,尝试”经过中间顶点中转”能否得到更短的路径。

与标准矩阵乘法的对应:标准矩阵乘法中用求和与乘法,而此处将求和替换为 min,将乘法替换为 加法

4. 引理23.1(EXTEND 的正确性)

引理23.1

给定权重矩阵 ,其中对所有 。令 (当 时),(当 时)。则对 ,矩阵 中的元素 等于从顶点 到顶点 的==最多包含 条边==的最短路径权重。

5. SLOW-ALL-PAIRS-SHORTEST-PATHS

算法执行流程

  1. 初始化 L^(1) = W(权重矩阵)
  2. 依次计算 L^(2), L^(3), …, L^(n-1),每次用 EXTEND-SHORTEST-PATHS 扩展
  3. 每次扩展将路径边数上限从 m-1 增加到 m
  4. 返回 L^(n-1) 作为所有结点对最短路径
    A["初始化 L^(1) = W"] --> B["令 m = 2"]
    B --> C["L^(m) = EXTEND-SHORTEST-PATHS(L^(m-1), W)"]
    C --> D{"m <= n - 1?"}
    D -- 是 --> E["m = m + 1"]
    E --> C
    D -- 否 --> F["返回 L^(n-1)"]
SLOW-ALL-PAIRS-SHORTEST-PATHS(W)
1  n = W.rows
2  L^(1) = W
3  for m = 2 to n - 1
4      L^(m) = EXTEND-SHORTEST-PATHS(L^(m-1), W)
5  return L^(n-1)

SLOW-ALL-PAIRS-SHORTEST-PATHS

正确性: 由引理23.1, 等于从 的最多 条边的最短路径权重。由于简单路径最多包含 条边( 个顶点的路径最多 条边),若图中不存在从 可达的负权环,则最短路径一定是简单路径,因此 就是真正的最短路径权重。

时间复杂度:

  • EXTEND-SHORTEST-PATHS 的三重循环耗时
  • 共调用 次 EXTEND(从
  • 总时间: ==== =

空间复杂度: (存储两个 矩阵

6. FASTER-ALL-PAIRS-SHORTEST-PATHS(重复平方)

算法执行流程

  1. 初始化 L^(1) = W
  2. m = 1,重复倍增:L^(2m) = L^(m) x L^(m)(Min-Plus 乘法)
  3. 每次迭代将路径边数上限翻倍
  4. 直到 2m >= n - 1 时停止,返回最终矩阵
    A["初始化 L^(1) = W, m = 1"] --> B{"m < n - 1?"}
    B -- 是 --> C["L^(2m) = EXTEND-SHORTEST-PATHS(L^(m), L^(m))"]
    C --> D["m = 2m"]
    D --> B
    B -- 否 --> E["返回 L^(m)"]
FASTER-ALL-PAIRS-SHORTEST-PATHS(W)
1  n = W.rows
2  L^(1) = W
3  m = 1
4  while m < n - 1
5      L^(2m) = EXTEND-SHORTEST-PATHS(L^(m), L^(m))
6      m = 2m
7  return L^(m)

FASTER-ALL-PAIRS-SHORTEST-PATHS

核心思想——重复平方(Repeated Squaring):

  • 注意到 ,即 可以通过将 与自身做 乘法得到
  • 这类似于快速幂的思想:不是一步一步从 推到 ,而是指数级跳跃:

正确性: 乘法等价于 。因为: 这表示从 先走最多 条边到 ,再从 走最多 条边到 ,总共最多 条边。

时间复杂度:

  • 每次迭代执行一次 EXTEND-SHORTEST-PATHS,耗时
  • while 循环执行
  • 总时间: ==== =

空间优化: 原始算法需要存储 个矩阵,空间为 。可以只保留两个矩阵交替使用,将空间降至

7. 与矩阵乘法的深层关系

半环与标准矩阵乘法

定义热带半环(Tropical Semiring)

  • “加法”运算为 (取最小值),单位元为
  • “乘法”运算为 (普通加法),单位元为

在此半环上定义矩阵乘法:

这恰好就是 EXTEND-SHORTEST-PATHS 执行的操作。因此:

  • 半环上的 次幂)
  • 给出所有结点对的最短路径权重

与标准矩阵乘法的类比:

标准矩阵乘法 最短路径矩阵乘法
单位矩阵 (对角线为1)单位矩阵 (对角线为0)
结合律成立结合律成立
= 自乘 = 自乘

8. 与 Strassen 算法的结合

利用 Strassen 算法加速 APSP

既然 APSP 可以归约为 半环上的矩阵乘法,而 Strassen 算法将标准矩阵乘法从 优化到 ,那么能否直接套用?

关键观察: Strassen 算法只依赖于加法和乘法的分配律结合律,不依赖于具体的数值运算。 半环同样满足这些代数性质( 满足分配律:),因此 Strassen 的分治策略可以直接移植。

将 Strassen 应用于 矩阵乘法:

  • 将 Strassen 中的 替换为 ,将 替换为
  • 每次递归将 矩阵乘法分解为 7 个 矩阵乘法
  • 复杂度从 降低到

理论意义: 这是 APSP 问题的一个次立方算法。虽然实际中 23.2 Floyd-Warshall算法 常数因子更小,但 Strassen 方法证明了 APSP 可以突破立方复杂度。


补充理解与拓展

Min-Plus 矩阵乘法(Tropical Semiring)的广泛应用

矩阵乘法不仅用于最短路径,在许多领域都有重要应用:

  1. 运筹学与调度问题:关键路径法(CPM)中,项目完成时间可以通过 矩阵乘法计算(与 对偶)
  2. 语言与自动机理论:有限自动机的语言接受问题可以表示为 半环上的矩阵乘法,而最短路径是 的特例
  3. 图像处理:形态学运算(膨胀、腐蚀)可以用 矩阵运算表示
  4. 生物信息学:序列比对中的动态规划可以看作 矩阵乘法的变体

为什么叫”Tropical”(热带)? 这个名字来自巴西数学家 Imre Simon,他在圣保罗大学(位于热带地区)对这种代数结构做了大量研究。 半环也被称为”min-plus 代数”或”热带代数”。

来源:Min-plus algebra (Wikipedia); Butkovic, “Max-linear Systems: Theory and Algorithms” (Springer, 2010)

重复平方技术的通用性

重复平方(Repeated Squaring)是一种通用的算法加速技术,不仅限于最短路径:

应用场景操作加速效果
快速幂 的计算
APSP(本节) 矩阵幂
传递闭包布尔矩阵幂
Fibonacci 数矩阵幂

核心思想一致:利用运算的结合律,将线性次数的迭代压缩为对数次数。在 APSP 中,关键洞察是 ,即两次 步扩展等价于一次 步扩展。


易混淆点与辨析

混淆:逐步扩展矩阵乘法 vs Floyd-Warshall 算法

两种算法都解决 APSP 问题,但扩展方式完全不同:

维度逐步扩展(本节)Floyd-Warshall(23.2节)
扩展维度边数扩展:中间顶点集合扩展:
递推公式
慢速复杂度
快速复杂度(重复平方)(就地更新)
直觉”路径最多能走几步""路径最多能经过哪些中间点”

关键区别: 逐步扩展的”慢速”版本是 ,而 Floyd-Warshall 直接就是 。逐步扩展需要借助重复平方才能达到 ,仍比 Floyd-Warshall 多一个 因子。但逐步扩展的理论价值在于揭示了 APSP 与矩阵乘法的深刻联系。

混淆: vs 的差异来源

  • (SLOW版本): EXTEND-SHORTEST-PATHS 本身是 (三重循环),调用 次,所以
  • (FAST版本): 同样是 的 EXTEND,但只调用 次(重复平方),所以
  • 加速的核心是利用了结合律,一次乘法将路径边数上限翻倍

混淆: 的含义

  • :从 最多 条边的最短路径权重(本节)
  • :从 所有中间顶点编号不超过 的最短路径权重(23.2节)
  • 两者刻画路径的方式不同,不要混淆

习题精选

题号题目描述难度
23.1-1对图25.2中的加权有向图运行 SLOW-ALL-PAIRS-SHORTEST-PATHS 和 FASTER-ALL-PAIRS-SHORTEST-PATHS,展示每次迭代得到的矩阵⭐⭐
23.1-2为什么要求对所有
23.1-3矩阵 (对角线为0,其余为 )在常规矩阵乘法中对应什么?
23.1-4证明 EXTEND-SHORTEST-PATHS 定义的矩阵乘法满足结合律⭐⭐⭐
23.1-5如何将单源最短路径问题表示为矩阵与向量的乘积?⭐⭐
23.1-6如何从完成的最短路径权重矩阵 时间内计算出前驱矩阵 ⭐⭐

视频学习指南

资源主题链接说明
MIT 6.006 Lecture 16Graph APSPhttps://www.youtube.com/watch?v=KXzJ7dOqTqIMIT公开课,涵盖APSP的矩阵乘法方法
Abdul BariFloyd-Warshall & APSPhttps://www.youtube.com/watch?v=oNI0rf2P9gE直观的逐步演示
WilliamFisetFloyd Warshallhttps://www.youtube.com/watch?v=4OQeClyHbew包含代码实现与复杂度分析
GeeksforGeeksAPSPhttps://www.geeksforgeeks.org/floyd-warshall-algorithm-dp-16/文字+图示,适合对照学习

教材原文

CLRS 第4版 23.1节原文

To solve the all-pairs shortest-paths problem on an input graph , we need to compute the shortest-path weight for every pair of vertices . We can enumerate all pairs, running a single-source shortest-paths algorithm from each vertex, but doing so takes time, where is the running time of the single-source algorithm. If we use Bellman-Ford, which runs in time, the total time becomes . If all edge weights are nonnegative, we can use Dijkstra’s algorithm, which runs in time with a binary min-heap, yielding a total time of .

This section presents two dynamic-programming algorithms for the all-pairs shortest-paths problem. The first, which we call SLOW-ALL-PAIRS-SHORTEST-PATHS, computes the shortest-path weights by exploiting the relationship between the all-pairs shortest-paths problem and the matrix multiplication problem. The second, FASTER-ALL-PAIRS-SHORTEST-PATHS, computes the same result more quickly by using the technique of “repeated squaring.”


参见Wiki

第23章-所有结点对的最短路径 最短路径与矩阵乘法