本站内容由 AI 生成,可能存在错误。如发现问题,欢迎到 GitHub Issues 反馈。

最小生成树:最便宜地连通所有人

最小生成树:最便宜地连通所有人

更新于 2026-04-24

Part 2”量”建立了一整套度量工具:最短路径量距离、中心性量重要性、社区发现量归属、图相似量结构、团与密子图量致密。但”量”是描述性的——告诉你图什么样的。

现在进入 Part 3”优”——图上的组合优化。不再只是观察,而是要做决策:从海量可能中选出最好的方案。第一个问题,也是组合优化最经典的入门问题:

给定一张加权无向图 G=(V,E)G = (V, E),选择哪些边,用最小的总权重把所有节点连通起来?

这就是最小生成树(Minimum Spanning Tree, MST)问题。它无处不在——铺设连接所有城市的光缆网络、设计最低成本的电路板连线、构建层次聚类的树形结构——本质上都在回答”用最少的代价把所有人连到一起”。

更重要的是,MST 是贪心算法的典范。在 最短路径 中,Dijkstra 的贪心只在非负权时有效,Bellman-Ford 用迭代逼近作为后备。而在 MST 问题上,贪心总是能给出最优解——这个优美的事实背后有深刻的结构性原因(拟阵理论),理解它将为你分析”何时贪心有效”提供强大的理论武器。

算法范式:贪心(Kruskal、Prim、Boruvka)

核心问题:最便宜的连通方案

形式化定义

给定连通的加权无向图 G=(V,E)G = (V, E)n=Vn = |V|m=Em = |E|,每条边 ee 有权重 w(e)0w(e) \geq 0

生成树(spanning tree)TTGG 的一个子图,满足:

  1. TT 包含 GG 的所有 nn 个顶点
  2. TT连通
  3. TT无环的(即是一棵树)

这三个条件等价于:TT 恰好有 n1n - 1 条边,且连通。

最小生成树(MST)是所有生成树中边权总和最小的那个:

T=argminT 是生成树eTw(e)T^* = \arg\min_{T \text{ 是生成树}} \sum_{e \in T} w(e)

为什么不是简单问题?

一张 nn 个节点的完全图有 nn2n^{n-2} 棵不同的生成树(Cayley 公式)。n=20n = 20 时就有 20182.6×102320^{18} \approx 2.6 \times 10^{23} 种可能——暴力枚举不可行。MST 算法的精妙之处在于:用贪心策略直接构造最优解,完全绕过枚举

核心思路:割性质与环性质

为什么贪心能构造出最优的生成树?答案归结为两个互补的性质——它们是所有 MST 算法正确性的基石。

割性质(Cut Property)

(cut)δ(S)\delta(S) 是一个划分 (S,VS)(S, V \setminus S)SV\emptyset \neq S \subsetneq V)所跨越的边集——即一端在 SS、另一端在 VSV \setminus S 的所有边。

割性质:对于图 GG 的任意割 δ(S)\delta(S),如果某条跨越边 ee 是该割中权重严格最小的(假设权重互不相同),那么 ee 一定属于 GG 的唯一 MST。

直觉:要连通 SSVSV \setminus S,至少得选一条跨越边。既然必须选一条,为什么不选最轻的?

割性质:割中最轻的跨越边一定属于某个 MST割性质(Cut Property)割 δ(S)SV \ S3142635274ABCDEF割中最轻边 (w=2)一定属于某个 MST

证明思路(反证法):假设 MST TT^* 不包含割中最轻边 e=(u,v)e = (u, v)TT^* 中一定有另一条跨越边 ee' 连接 SSVSV \setminus S(因为 TT^* 连通)。把 ee 加入 TT^* 形成一个环,这个环必然包含 ee'(因为 eeee' 都跨越同一个割)。删除 ee'、保留 ee,得到的仍然是生成树,但权重更小——矛盾。

环性质(Cycle Property)

环性质:对于图 GG 中的任意环 CC,如果某条边 eeCC 中权重严格最大的,那么 ee 一定属于任何 MST。

直觉:环中最重的边是”多余的”——删掉它,环中的其他边仍然保证连通性,而且总权重更小。

环性质:环中最重的边一定不属于任何 MST环性质(Cycle Property)环 A-B-C-D-A342518ABCDE环中最重边 (w=8)一定不属于任何 MST

证明思路(反证法):假设 MST TT^* 包含环 CC 中的最重边 ee。删除 eeTT^* 分成两个连通分量,形成一个割。CC 中一定还有至少一条其他边 ee' 跨越这个割(因为 CCee 两侧各有部分)。用 ee' 替换 ee,得到权重更小的生成树——矛盾。

两个性质如何指导算法?

  • Kruskal:按权重从小到大检查边。如果一条边连接两个不同的连通分量,它就是某个割的最轻边 → 选入(割性质)。如果连接同一分量,它是某个环的最重边 → 丢弃(环性质)。
  • Prim:从一个节点出发,每次从”树内节点”到”树外节点”的割中选最轻边 → 选入(割性质)。
  • Boruvka:每个连通分量同时选它的割中最轻边 → 全部选入(割性质的并行应用)。

Kruskal 算法:排序 + 并查集

核心思想

Kruskal 算法(1956)的策略极其直觉:

把所有边按权重从小到大排序。依次检查每条边:如果它连接两个不同的连通分量,就选入 MST;如果连接同一分量(选入会形成环),就跳过。

关键问题是如何快速判断”两个节点是否在同一分量”以及”合并两个分量”——这正是并查集(Union-Find / Disjoint Set Union)数据结构的用武之地。

算法流程

Kruskal(G):
    将 E 按权重排序
    初始化并查集:每个节点是独立分量
    MST = 空集

    for each edge (u, v, w) in sorted order:
        if Find(u) ≠ Find(v):       // 不同分量
            MST = MST ∪ {(u, v)}
            Union(u, v)              // 合并分量
        // else: 跳过(会形成环)

    return MST
Kruskal 算法:排序边 → 逐边检查(Find)→ 合并分量(Union)Kruskal 算法 + 并查集(Union-Find)① 按权重排序$O(E \log E)$② 逐边检查 (Find)同分量?→ 跳过③ 合并分量 (Union)不同分量 → 选入 MST边按权重排序后逐一处理:C-F: 1✓ 不同分量,选入A-B: 2✓ 不同分量,选入D-E: 2✓ 不同分量,选入B-C: 3✓ 不同分量,选入A-D: 4✗ 同一分量,跳过E-F: 4✓ 不同分量,选入B-D: 5✗ 同一分量,跳过C-E: 6✗ 同一分量,跳过并查集(Union-Find)优化路径压缩(Path Compression):Find 时直接指向根 → 树高趋近 O(1)按秩合并(Union by Rank):小树挂到大树上 → 合并后高度增长缓慢两者结合:每次操作均摊 O(α(n)) ≈ O(1)

并查集(Union-Find)

并查集维护一组不相交集合,支持两个核心操作:

  • Find(x):返回 xx 所在集合的代表元素(根)
  • Union(x, y):合并 xxyy 所在的两个集合

朴素实现每次操作 O(n)O(n)(最坏情况下树退化为链)。两个优化将均摊复杂度降到几乎常数:

路径压缩(Path Compression):在 Find 操作中,让路径上的每个节点直接指向根。下次再查,一步到位。

Find(x):
    if parent[x] ≠ x:
        parent[x] = Find(parent[x])  // 路径压缩
    return parent[x]

按秩合并(Union by Rank):Union 时把较矮的树挂到较高的树下面,防止树的高度增长过快。

Union(x, y):
    rx = Find(x), ry = Find(y)
    if rx == ry: return
    if rank[rx] < rank[ry]: parent[rx] = ry
    elif rank[rx] > rank[ry]: parent[ry] = rx
    else: parent[ry] = rx; rank[rx]++

两者结合后,每次操作的均摊时间复杂度为 O(α(n))O(\alpha(n)),其中 α\alpha逆 Ackermann 函数(inverse Ackermann function)。α(n)\alpha(n) 增长极其缓慢——对于宇宙中所有原子的数量(约 108010^{80}),α(n)4\alpha(n) \leq 4。实践中可以视为 O(1)O(1)

复杂度分析

  • 排序O(mlogm)=O(mlogn)O(m \log m) = O(m \log n)(因为 mn2m \leq n^2logm2logn\log m \leq 2\log n
  • 并查集操作mm 次 Find + 至多 n1n - 1 次 Union = O(mα(n))O(m \cdot \alpha(n))
  • 总计O(mlogm)O(m \log m),瓶颈在排序

数值走查

考虑 7 个节点的加权无向图(与交互组件中使用的相同图):

边按权重排序:C-F(1), A-E(2), D-G(2), B-C(3), F-G(3), A-B(4), E-F(4), B-E(5), C-D(6), F-D(7)

  1. C-F(1):C 和 F 在不同分量 → 选入。分量:{A},{B},{C,F},{D},{E},{G}\{A\}, \{B\}, \{C,F\}, \{D\}, \{E\}, \{G\}
  2. A-E(2):A 和 E 在不同分量 → 选入。分量:{A,E},{B},{C,F},{D},{G}\{A,E\}, \{B\}, \{C,F\}, \{D\}, \{G\}
  3. D-G(2):D 和 G 在不同分量 → 选入。分量:{A,E},{B},{C,F},{D,G}\{A,E\}, \{B\}, \{C,F\}, \{D,G\}
  4. B-C(3):B 和 C 在不同分量 → 选入。分量:{A,E},{B,C,F},{D,G}\{A,E\}, \{B,C,F\}, \{D,G\}
  5. F-G(3):F 在 {B,C,F}\{B,C,F\},G 在 {D,G}\{D,G\},不同分量 → 选入。分量:{A,E},{B,C,F,D,G}\{A,E\}, \{B,C,F,D,G\}
  6. A-B(4):A 在 {A,E}\{A,E\},B 在 {B,C,F,D,G}\{B,C,F,D,G\},不同分量 → 选入。分量:{A,B,C,D,E,F,G}\{A,B,C,D,E,F,G\}

已选 6 = n1n - 1 条边,MST 完成!总权重 = 1+2+2+3+3+4=151 + 2 + 2 + 3 + 3 + 4 = 15

剩余边 E-F(4)、B-E(5)、C-D(6)、F-D(7) 全部跳过(会形成环)。

Prim 算法:贪心扩展 + 优先队列

核心思想

Prim 算法(1957)从一个起始节点出发,逐步”生长”MST:

维护一个”树内”节点集合 SS。每次从割 δ(S)\delta(S) 中选出最轻边,将新节点纳入 SS。重复直到所有节点都在树内。

这和 Dijkstra 的结构几乎一模一样——区别只在优先队列的排序依据

  • Dijkstra:按 d[v]=d[u]+w(u,v)d[v] = d[u] + w(u, v)(从源点到 vv 的路径长度)
  • Prim:按 key[v]=w(u,v)\text{key}[v] = w(u, v)(从 vv 到树的最轻边权重)

算法流程

Prim(G, start):
    for each v ∈ V:
        key[v] = ∞
        parent[v] = nil
    key[start] = 0
    PQ = min-priority queue with all v, keyed by key[v]

    while PQ is not empty:
        u = PQ.extract-min()
        for each edge (u, v) with weight w:
            if v ∈ PQ and w < key[v]:   // 注意:是 w,不是 key[u]+w
                key[v] = w
                parent[v] = u
                PQ.decrease-key(v, w)

    return parent (定义了 MST 的边)

与 Dijkstra 的对比

Prim 与 Dijkstra:结构几乎一样,但优化目标不同Prim vs Dijkstra — 结构几乎一样,目标不同唯一的代码区别:松弛时用 w(u,v) 还是 d[u]+w(u,v)维度Prim (MST)Dijkstra (SPT)优先级key[v] = 到树的最轻边权d[v] = 到源点的最短路径长松弛目标key[v] = min(key[v], w(u,v))d[v] = min(d[v], d[u]+w(u,v))产出最小生成树 (MST)最短路径树 (SPT)全局最优?边权总和最小每个节点到源距离最短

关键区别用一句话概括:Dijkstra 累加路径长度,Prim 只看单条边的权重。Dijkstra 让每个节点到源点的距离最短;Prim 让每个节点到树的连接最便宜。

这个区别导致了不同的结果:Dijkstra 输出最短路径树(SPT,每个节点到源的距离最短),Prim 输出最小生成树(MST,所有边权总和最小)。两棵树通常不同——SPT 优化的是”个体到源的距离”,MST 优化的是”全局连接成本”。

复杂度分析

与 Dijkstra 相同:

优先队列实现总时间
数组(线性扫描)O(V2)O(V^2)
二叉堆O((V+E)logV)O((V+E)\log V)
Fibonacci 堆O(VlogV+E)O(V\log V + E)
  • 稠密图EV2E \approx V^2):数组版 O(V2)O(V^2) 最优
  • 稀疏图EVE \approx V):二叉堆版 O((V+E)logV)O((V+E)\log V) 最实用

Kruskal vs Prim:如何选择?

  • Kruskal 的瓶颈是排序,不依赖图的结构 → 适合稀疏图mm 小时排序快)
  • Prim 的效率取决于优先队列 → 适合稠密图V2V^2 版在稠密图上不比 Kruskal 的 O(mlogm)O(m \log m) 差)
  • Kruskal 天然处理不连通的图(输出最小生成森林),Prim 需要连通图

交互式走查

下面的组件在同一张加权无向图上展示 Kruskal 和 Prim 的完整执行过程。切换算法观察它们的不同策略:Kruskal 全局排序后逐边决策,Prim 从起点贪心扩展。

MST 算法走查
4235612437ABCDEFG未处理当前检查已选入 MST
分量数:7
步骤 1/14: Kruskal 初始化:每个节点是独立分量。边按权重排序:C-F(1), A-E(2), D-G(2), B-C(3), F-G(3), A-B(4), E-F(4), B-E(5), C-D(6), F-D(7)
1 / 14

注意关键区别:

  • Kruskal 按边权从小到大全局处理,并查集判断是否成环
  • Prim 从 A 出发,每步选”到树最近”的节点加入,用优先队列维护候选

两者在同一张图上输出相同的 MST(权重互不相同时,MST 唯一),但构建顺序不同。

为什么贪心在 MST 上总是有效?拟阵理论的直觉

最短路径 中我们看到:Dijkstra 的贪心在负权边面前失效。为什么 MST 的贪心不会失效?答案来自一个深刻的代数结构——拟阵(Matroid)。

拟阵的定义(直觉版)

拟阵 (E,I)(E, \mathcal{I}) 是一个集合 EE(“基础集”)和 EE 的子集族 I\mathcal{I}(“独立集族”),满足三条公理:

  1. 空集独立I\emptyset \in \mathcal{I}
  2. 遗传性:如果 BIB \in \mathcal{I}ABA \subseteq B,则 AIA \in \mathcal{I}(独立集的子集也独立)
  3. 交换性:如果 A,BIA, B \in \mathcal{I}A<B|A| < |B|,则存在 bBAb \in B \setminus A 使得 A{b}IA \cup \{b\} \in \mathcal{I}

这是对”线性无关”和”无环”概念的统一抽象。拟阵中的极大独立集(无法再扩展的独立集)称为(basis),所有基的大小相同。

图拟阵(Graphic Matroid)

给定图 G=(V,E)G = (V, E),定义:

  • 基础集 = EE(所有边)
  • 独立集 = EE不构成环的边集(即森林

验证三条公理:

  1. 空的边集没有环 ✓
  2. 森林的子集仍是森林 ✓
  3. 较小的森林可以从较大的森林”借”一条边扩展(因为较大的森林连通分量更少,一定有一条边跨越较小森林的某个割)✓

图拟阵的基 = 生成树(恰好 n1n - 1 条边的连通无环子图)。

贪心在拟阵上的最优性

拟阵理论直觉:图的无环边集构成拟阵,贪心在拟阵上一定最优拟阵(Matroid)— 贪心为什么能给最优解?拟阵是什么?集合族 (E, I),满足:1. ∅ ∈ I(空集是独立的)2. 遗传性:A ⊂ B ∈ I → A ∈ I3. 交换性:|A| < |B|, ∃ b ∈ B\A 使 A∪{b} ∈ I直觉:"独立集"的公理化——抽象了"线性无关"和"无环"的共性图拟阵(Graphic Matroid)E = 图的所有边I = 不构成环的边集 (即"森林")验证拟阵公理:✓ 空集无环 ✓✓ 森林的子集仍是森林 ✓✓ 小森林可从大森林 "借"一条边扩展 ✓极大独立集 = 生成树(n-1 条边)贪心定理Rado-Edmonds 定理:对任意加权拟阵,贪心按权重排序选择一定得到最优独立集。→ 对图拟阵: 按权重从小到大选边 只选不成环的 = Kruskal 算法 → 一定得到 MST ✓

Rado-Edmonds 定理:对于任意加权拟阵 (E,I)(E, \mathcal{I}),以下贪心算法总能找到最小权重的基

Greedy-Matroid(E, I, w):
    将 E 按权重从小到大排序
    B = ∅
    for each e ∈ E in sorted order:
        if B ∪ {e} ∈ I:  // 加入 e 后仍独立
            B = B ∪ {e}
    return B

将这个定理应用到图拟阵:EE = 边集,I\mathcal{I} = 无环边集,"B{e}IB \cup \{e\} \in \mathcal{I}" = “加入 ee 不形成环”——这就是 Kruskal 算法

这就是贪心在 MST 上总有效的根本原因:图的无环边集构成一个拟阵,而贪心在拟阵上一定找到最优基。这不是巧合,是结构性保证。

反过来,当你遇到一个新问题想用贪心时,可以问自己:这个问题的可行解集构成拟阵吗? 如果是,大胆用贪心;如果不是,贪心可能失败。

Boruvka 算法:并行友好的 MST

核心思想

Boruvka 算法(1926年,是最早的 MST 算法!)的策略天然适合并行:

每一阶段,每个连通分量同时选出它的割中最轻边。所有被选的边加入 MST,对应的分量合并。重复直到只剩一个分量。

Boruvka 算法:每个分量同时选最轻出边,O(log V) 轮合并完成Boruvka 算法 — 每个分量同时选最轻出边阶段 0:每个节点是独立分量3546217ABCDEF阶段 1:每个分量选最轻出边3546217ABCDEF阶段 2:合并后再选3546217ABCDEF每阶段分量数至少减半 → 最多 O(log V) 阶段 → 天然适合并行

算法流程

Boruvka(G):
    MST = ∅
    每个节点初始为独立分量

    while 分量数 > 1:
        for each 分量 C:
            找到 C 的割中最轻边 e_C
        for each 选出的 e_C:
            if e_C 连接两个不同分量:
                MST = MST ∪ {e_C}
                合并对应分量

    return MST

为什么并行友好?

每一阶段中,所有分量独立地选择最轻出边——这些选择互不干扰,可以完全并行。每阶段结束后,分量数至少减半(每个分量至少与另一个合并),因此最多 O(logn)O(\log n) 阶段。

  • 串行时间O(mlogn)O(m \log n)(每阶段 O(m)O(m) 扫描所有边,O(logn)O(\log n) 阶段)
  • 并行时间O(log2n)O(\log^2 n)(在 PRAM 模型上,使用 O(m)O(m) 个处理器)

Boruvka 是现代并行 MST 算法的基础,在 GPU 和分布式系统上被广泛使用。

复杂度对比

算法时间复杂度(最坏)空间适用场景
KruskalO(mlogm)O(m \log m)O(m+n)O(m + n)稀疏图;不要求连通
Prim(二叉堆)O((n+m)logn)O((n+m)\log n)O(n)O(n)稠密图;从特定节点出发
Prim(数组)O(n2)O(n^2)O(n)O(n)稠密图(mn2m \approx n^2
Prim(Fibonacci 堆)O(nlogn+m)O(n\log n + m)O(n)O(n)理论最优(实践常数大)
BoruvkaO(mlogn)O(m \log n)O(m+n)O(m + n)并行环境;分布式计算

注:所有复杂度均为最坏情况。当边权互不相同时,MST 唯一;否则可能有多个权重相同的 MST。

选择经验法则

  1. 一般稀疏图 → Kruskal(实现简单,并查集库随处可得)
  2. 稠密图 → Prim + 数组(O(n2)O(n^2)mn2m \approx n^2 时最优)
  3. 需要并行 → Boruvka
  4. 竞赛/工程 → Kruskal(代码最短、最不易出错)

Steiner Tree:只连通一部分节点

MST 要求连通所有节点。如果我们只需要连通其中一个子集(称为终端节点,terminal),允许使用其余节点作为中继(Steiner 点)呢?

这就是 Steiner Tree 问题:给定图 GG 和终端集合 RVR \subseteq V,找一棵连通 RR 中所有节点的最小权重树。

MST 连通所有节点(P),Steiner Tree 只连通指定终端(NP-hard)MST vs Steiner Tree — P vs NP-hard必须连通的终端节点可选的中继节点MST(连通所有 6 个节点)3243123ABCDEF总权重: 11Steiner Tree(只需连通 A,C,F)3243123ABCDEF总权重: 6vsMST 要求连通所有节点 → 多项式时间 | Steiner Tree 只需连通指定子集 → NP-hard

P vs NP-hard 的鲜明对比

  • MST(连通所有节点):多项式时间 O(mlogn)O(m \log n)
  • Steiner Tree(连通指定子集):NP-hard

差异在哪里?MST 中,“哪些节点要连通”已经确定(全部),算法只需决定”选哪些边”。Steiner Tree 中,还需要决定”经过哪些中继节点”——这个额外的组合选择引入了指数级的搜索空间。

R=VR = V 时,Steiner Tree 退化为 MST。当 R|R| 很小时,可以用动态规划在 O(3Rn+2Rn2)O(3^{|R|} \cdot n + 2^{|R|} \cdot n^2) 时间内精确求解(Dreyfus-Wagner 算法)。一般情况下常用近似算法,其中最简单的一种就是用 MST 近似

在终端节点的完全图(用原图中的最短路径作为边权)上求 MST。这给出 Steiner Tree 的 2-近似——最坏是最优解的 2 倍。

这是 MST 作为近似算法子程序的一个典型例子。

应用场景

MST 在网络布线、聚类、近似算法、图像分割中的应用MST 应用场景🔌网络布线最小成本连通所有节点🧬聚类分析Single-linkage= Kruskal 变体🗺️近似算法Christofides用 MST 做 TSP🖼️图像分割MST 切割分离区域MST 是图优化的基础子程序——许多高级算法以 MST 为起点

网络布线:最小成本连通

MST 最直接的应用就是网络设计。假设你要用光缆连接 nn 个数据中心,任意两个之间铺设光缆的成本已知。MST 给出总成本最低的连接方案

实际案例:AT&T 的长途电话网络规划在 20 世纪 50 年代就是用 Prim/Kruskal 算法的变种来设计的——这也是 Prim 算法发表在 Bell System Technical Journal 的历史背景。

聚类分析:Single-Linkage Hierarchical Clustering

层次聚类(hierarchical clustering)中最常用的 single-linkage 方法本质上就是 Kruskal 算法的变体:

  1. 计算所有数据点之间的距离,构建完全图
  2. 运行 Kruskal 算法——每次选最短边时,就是在合并两个最近的簇
  3. Kruskal 执行过程中的合并顺序就是一棵树状图(dendrogram)
  4. 在任意高度”切割”树状图,得到不同粒度的聚类

这个等价关系揭示了一个优雅的事实:single-linkage 聚类的树状图恰好由 MST 唯一确定。修改聚类粒度不需要重新计算——只需在 MST 上删除不同的边。

近似算法子程序:TSP 与 Christofides

旅行商问题(TSP)是另一个 NP-hard 问题:找一个经过所有节点恰好一次的最短环路。Christofides 算法(1976)是最著名的 TSP 近似算法,它的第一步就是计算 MST

  1. 计算 MST TT
  2. TT 中度为奇数的节点,在它们上做最小权重完美匹配
  3. 合并 MST 和匹配,构造欧拉回路
  4. 跳过重复节点得到 TSP 近似解

这个算法保证解的质量不超过最优解的 1.5 倍——在 NP-hard 问题上,这是一个极好的保证。MST 在这里提供了 TSP 最优解的下界:任何 TSP 解删掉一条边就是一棵生成树,因此 MSTOPTTSP\text{MST} \leq \text{OPT}_{TSP}

图像分割

在计算机视觉中,图像可以建模为图:像素是节点,相邻像素之间有边,边权是颜色/亮度差异。MST 上权重最大的边对应图像中最显著的”边界”——删除这些边就把图像分割成不同的区域。Felzenszwalb & Huttenlocher(2004)的高效图像分割算法正是基于这个思路。

🔁 迭代机器视角PrimOPT: 优化目标

Graph无向加权图
State[v]key(v) = 到已选集合的最小边权
SELECTPQ-min(最小权边优先)
COMBINE扫描邻接边 → 更新 key(v)
重入无(label-setting)
TERMINATEFrontier 空(所有节点已选)
求解方法FI

💡 对比: Dijkstra: 结构完全相同,仅 COMBINE 不同——Dijkstra 用 d(u)+w, Prim 用 w

→ 框架详解:Art. 0A — 图上的通用迭代机器

🔁 迭代机器视角KruskalOPT: 优化目标

Graph边列表
State[v]Union-Find 集合
SELECT全局排序(边权升序)
COMBINEUnion-Find 检查是否跨越不同分量
TERMINATE已选 V-1 条边
求解方法GS

Kruskal 不是 Frontier 迭代——它是贪心扫描 (GS),按排序后的边列表逐一决策

→ 框架详解:Art. 0A — 图上的通用迭代机器

🔁 迭代机器视角BorůvkaOPT: 优化目标

Graph无向加权图 → 逐步收缩
State[v]分量归属
SELECT并行——每个分量选最轻出边
COMBINE合并分量
重入有界(log V 轮)
TERMINATE只剩一个分量
求解方法FI

→ 框架详解:Art. 0A — 图上的通用迭代机器

总结

本文开启了 Part 3”优”,从 MST 这个组合优化的经典入门问题出发,建立了以下工具和洞察:

  • 割性质与环性质是 MST 正确性的两个基石——割中最轻边必须选,环中最重边必须弃
  • Kruskal:全局排序 + 并查集判环,O(mlogm)O(m \log m),适合稀疏图
  • Prim:从起点贪心扩展 + 优先队列,结构与 Dijkstra 几乎一样,但松弛的是”到树的距离”而非”到源的距离”
  • Boruvka:每个分量同时选最轻出边,天然并行,O(logn)O(\log n) 阶段
  • 拟阵理论:图的无环边集构成拟阵 → 贪心在拟阵上一定最优 → Kruskal 一定正确。这是判断”贪心何时有效”的强大理论框架
  • Steiner Tree:只连通部分节点 → NP-hard,与 MST 的多项式时间形成鲜明对比
  • 应用:网络布线、层次聚类(single-linkage = Kruskal 变体)、TSP 近似(Christofides 用 MST 做下界)、图像分割

MST 展示了组合优化的第一个主题:贪心策略在正确的结构下可以给出最优解。但大多数组合优化问题没有拟阵结构——贪心会失败。下一篇 Art. 12: 网络流 将展示另一种更强大的优化范式——增广路方法:找还能改善的路径,沿路径推进,直到最优。

实现指引

算法函数/方法
MST(默认 Kruskal)NetworkXnx.minimum_spanning_tree(G, algorithm='kruskal')
MST(Prim)NetworkXnx.minimum_spanning_tree(G, algorithm='prim')
MST(Boruvka)NetworkXnx.minimum_spanning_tree(G, algorithm='boruvka')
MST 权重NetworkXnx.minimum_spanning_tree(G).size(weight='weight')
MST 边列表NetworkXnx.minimum_spanning_edges(G, data=True)
MST(稀疏矩阵)scipyscipy.sparse.csgraph.minimum_spanning_tree(graph)
MSTigraphg.spanning_tree(weights='weight')
Steiner Tree(近似)NetworkXnx.algorithms.approximation.steiner_tree(G, terminal_nodes)