最小生成树唯一性判定
问题描述
给定一个无向连通图 \(G=(V, E)\),每条边 \(e\) 有非负边权 \(w(e)\)。我们需要判断图 \(G\) 的最小生成树(Minimum Spanning Tree, MST)是否唯一。也就是说,是否存在多棵不同的生成树,其总权重都等于最小生成树的权重。
核心挑战
通常我们通过 Kruskal 或 Prim 算法可以找到一棵 MST,但这两类算法在边权相等时可能产生不同的 MST(尽管总权重相同)。判断唯一性需要检查是否存在另一棵总权重相同的生成树,其边集与已找到的 MST 不完全相同。
解题思路
我们可以基于以下关键观察来设计判定算法:
如果一棵 MST 是唯一的,那么对于图中每条不在 MST 中的边 \(e\),将它加入 MST 会形成一个环,而这条边 \(e\) 必须是这个环上严格最大的边(即环上不能有任何其他边的权重等于 \(e\) 的权重)。
反之,如果存在一条非 MST 边 \(e\),加入 MST 形成的环中有一条与 \(e\) 权重相等的边,则我们可以用 \(e\) 替换那条等权的 MST 边,得到另一棵总权重相同的生成树,此时 MST 不唯一。
因此,判定算法可分为两步:
- 用任意 MST 算法(如 Prim 或 Kruskal)求出任意一棵 MST,记其边集为 \(T\)。
- 对每条不在 \(T\) 中的边 \(e = (u, v)\),在 \(T\) 中找到 \(u\) 到 \(v\) 的路径上的最大边权。如果该最大边权等于 \(w(e)\),则 MST 不唯一;否则继续检查。若所有非树边检查后均未找到等权替换边,则 MST 唯一。
逐步讲解
步骤 1:求一棵最小生成树
- 假设图有 \(n\) 个顶点、\(m\) 条边。我们用 Prim 算法(从任意起点开始,逐步加入离当前树最近的顶点)或 Kruskal 算法(按边权升序加边,避免环)求 MST。
- 这里以 Kruskal 为例,因为它便于获得边集。
- 将边按权重从小到大排序,用并查集维护连通分量。依次处理每条边,若边的两端不在同一连通分量,则将该边加入 MST 边集 \(T\),并合并两端所在的集合。
- 时间开销:排序 \(O(m \log m)\),并查集操作近似 \(O(m \alpha(n))\),其中 \(\alpha\) 是反阿克曼函数。
- 得到 MST 边集 \(T\),并记录下 MST 的总权重 \(W_{\text{mst}}\)。
步骤 2:构建 MST 的树结构,用于快速查询路径最大边权
- 将 \(T\) 看作一棵树(因为 MST 是无环连通子图,恰有 \(n-1\) 条边)。
- 我们需要高效回答查询:给定树上两个节点 \(u\) 和 \(v\),找出 \(u\) 到 \(v\) 的路径上最大的边权。这可以通过以下任一方法实现:
- 方法 A:倍增法(Binary Lifting)
- 任选一个根(如节点 1),进行深度优先搜索(DFS),计算每个节点的深度 \(\text{depth}[u]\) 和向上 \(2^k\) 级的祖先 \(\text{anc}[u][k]\),同时记录从 \(u\) 到其 \(2^k\) 级祖先的路径上的最大边权 \(\text{maxEdge}[u][k]\)。
- 预处理:
- 初始时,\(\text{anc}[u][0]\) 是 \(u\) 的父节点,\(\text{maxEdge}[u][0]\) 是连接 \(u\) 与父节点的边的权重。
- 递推:\(\text{anc}[u][k] = \text{anc}[ \text{anc}[u][k-1] ][k-1]\),
\(\text{maxEdge}[u][k] = \max( \text{maxEdge}[u][k-1], \text{maxEdge}[ \text{anc}[u][k-1] ][k-1] )\)。
- 查询 \(u\) 到 \(v\) 路径上的最大边权:
- 如果 \(u\) 和 \(v\) 深度不同,先将较深的节点上移,同时更新路径最大边权。
- 然后从大到小尝试 \(k\)(如 \(k = \lceil \log n \rceil\) 递减到 0),若 \(\text{anc}[u][k] \neq \text{anc}[v][k]\),则同时上移 \(u\) 和 \(v\),并更新最大边权为两段的最大值。
- 最后 \(u\) 和 \(v\) 在同一深度且父亲相同(即 LCA 的直接子节点),此时路径最大边权为已记录的最大值与连接 \(u, v\) 到 LCA 的两条边权重的最大值。实际上,在实现中,我们可以在上移过程中始终更新最大边权,直到 \(u = v\),返回记录的最大值。
- 方法 B:树链剖分(Heavy-Light Decomposition, HLD)
将树分解为若干重链,用线段树或树状数组维护每条链上的边权最大值。查询时,在 \(u\) 和 \(v\) 向 LCA 跳转的过程中,查询每条重链区间的最大值,再取整体最大。HLD 预处理 \(O(n)\),每次查询 \(O(\log^2 n)\)。 - 方法 C:离线处理(如用 Kruskal 重构树)
也可以利用 Kruskal 重构树的性质:MST 中 \(u\) 到 \(v\) 路径上的最大边权等于重构树中 \(u\) 和 \(v\) 的 LCA 的权重。但需额外构建重构树。
- 方法 A:倍增法(Binary Lifting)
- 为简单起见,我们选用倍增法,因为它编码相对直接,且每次查询可在 \(O(\log n)\) 完成。
步骤 3:检查每条非树边
- 枚举原图的每条边 \(e = (u, v, w)\),若 \(e \notin T\)(即不是 MST 的边),则查询 \(T\) 中 \(u\) 到 \(v\) 路径上的最大边权 \(\text{maxWeight}\)。
- 如果 \(w == \text{maxWeight}\),说明存在另一棵总权重相同的 MST(将这条最大边替换为 \(e\) 即得),因此 MST 不唯一,算法可直接返回“不唯一”。
- 注意:如果 \(w < \text{maxWeight}\),则 \(e\) 比路径上某条边更小,这与 MST 的最小性矛盾(因为如果这样,当初用 \(e\) 替换那条更大边会得到更小的生成树),所以这种情况不会发生在 MST 已求出的前提下。实际上,由于我们按 Kruskal 顺序加边,任何非树边 \(e\) 的权重一定大于等于路径上所有边权(否则它会被加入 MST)。因此我们只需检查相等的情况。
- 遍历所有非树边后,若未发现等权情况,则 MST 唯一。
步骤 4:复杂度分析
- 求 MST:\(O(m \log m)\)。
- 构建倍增结构:DFS 遍历树 \(O(n)\),预处理 \(O(n \log n)\)。
- 检查非树边:最多 \(m\) 条边,每条边查询 \(O(\log n)\),总时间 \(O(m \log n)\)。
- 整体复杂度:\(O(m \log m + (n+m) \log n)\),在稀疏图(\(m = O(n)\))中约为 \(O(n \log n)\),在稠密图(\(m = O(n^2)\))中约为 \(O(n^2 \log n)\)。
- 空间复杂度:\(O(n \log n + m)\)。
举例说明
考虑一个简单图:四个顶点 \(\{A,B,C,D\}\),边权如下:
- \((A,B):1\)
- \((B,C):2\)
- \((C,D):1\)
- \((D,A):2\)
- \((A,C):3\)
- \((B,D):3\)
用 Kruskal 算法,按边权升序:先加 (A,B):1,(C,D):1(此时两个连通分量 {A,B} 和 {C,D}),然后考虑边权 2 的边 (B,C) 和 (D,A) 任选一条(比如选 (B,C):2)连接两个分量,得到 MST 总权重 4,边集 \(T = \{(A,B),(C,D),(B,C)\}\)。
现在检查非树边: - (D,A):2:在 MST 中 A 到 D 的路径是 A–B–C–D,路径边权为 {1,2,1},最大为 2,等于 (D,A) 的权重 2 ⇒ 可替换(例如用 (D,A) 替换 (B,C)),得到另一棵 MST {(A,B),(C,D),(D,A)},总权重也是 4。因此 MST 不唯一。
扩展思考
- 如果图中有重边(平行边),算法依然有效,因为每条边独立处理。
- 如果所有边权互不相同,则 MST 一定唯一(由 Cut 和 Cycle 性质保证)。
- 此算法也可用于“次小生成树”问题中计算严格次小生成树的第一步。
- 实际实现时,需注意浮点数边权的比较应使用误差容忍(如 1e-9)。
小结
最小生成树唯一性判定可通过“找到一棵 MST,然后检查每条非树边是否能在 MST 中替换等权边”解决。核心在于高效查询树上路径最大边权,常用倍增法或树链剖分实现。算法能在 \(O(m \log n)\) 左右时间内完成判定,适用于大多数实际场景。