← 返回题目列表

冗余连接问题为什么适合用并查集?

高频 中等 第 4 / 30 题 更新于 2026/07/30
并查集环检测无向图

简化版

冗余连接是在一棵树里多加了一条边,要求找出造成环的那条边。用并查集逐条处理边,如果两个端点已经在同一个集合里,再连这条边就会形成环,这条边就是冗余边。

详细版

并查集维护当前已经连接的连通块。对每条边 (u, v),先 find(u)find(v):如果根不同,说明这条边连接了两个不同连通块,可以 union;如果根相同,说明 u 和 v 之前已经连通,再加这条边就产生环,返回它。

因为题目通常保证原图是树加一条边,所以第一条 union 失败的边就是答案。路径压缩和按秩合并后,时间复杂度近似 O(E),空间复杂度 O(V)

完整版教学

一、为什么树加一条边一定会成环

一棵有 n 个节点的树有两个关键性质:连通,并且恰好有 n - 1 条边。任意两个节点之间只有一条简单路径。如果再加一条连接已有两个节点的边,就会和原来的那条路径围成一个环。

原树路径: 1 - 2 - 3
新增边:   1 ----- 3

环: 1 -> 2 -> 3 -> 1

冗余连接问的不是“图里有没有环”,而是“哪条输入边第一次让环出现”。这非常适合按输入顺序动态维护连通性。

二、并查集维护的是连通块

并查集有两个核心操作:find(x) 找 x 所在集合的代表节点,union(a,b) 合并两个集合。对于无向图,如果两个点已经在同一个集合里,说明它们之间已经有路径;再加入一条直接边,就必然形成环。

情况find(u) 与 find(v)动作含义
不同集合不相等union连接两个连通块
同一集合相等返回该边这条边制造环

例如边序列 [1,2] [1,3] [2,3]:前两条边把 1、2、3 合成一个集合;处理 [2,3] 时发现 2 和 3 已经连通,所以 [2,3] 是冗余边。

三、代码实现骨架

并查集实现要包含路径压缩,否则链式父指针可能退化。按秩合并或按大小合并可以进一步降低树高。

int[] findRedundantConnection(int[][] edges) {
    int n = edges.length;
    int[] parent = new int[n + 1];
    int[] rank = new int[n + 1];
    for (int i = 1; i <= n; i++) parent[i] = i;
    for (int[] e : edges) {
        if (!union(parent, rank, e[0], e[1])) return e;
    }
    return new int[0];
}

int find(int[] parent, int x) {
    if (parent[x] != x) parent[x] = find(parent, parent[x]);
    return parent[x];
}

boolean union(int[] parent, int[] rank, int a, int b) {
    int ra = find(parent, a), rb = find(parent, b);
    if (ra == rb) return false;
    if (rank[ra] < rank[rb]) parent[ra] = rb;
    else if (rank[ra] > rank[rb]) parent[rb] = ra;
    else { parent[rb] = ra; rank[ra]++; }
    return true;
}

这里 union 返回 false 表示“合并失败”,不是程序失败,而是两个端点已经属于同一集合。

四、为什么不用 DFS 每次判环

当然可以每加入一条边就 DFS 检查 u 和 v 是否已连通,但这样最坏会变成 O(E(V+E))。并查集把“是否连通”压缩成近似常数级查询,更适合边按顺序加入的题。

逐边 DFS:
edge1 -> 搜一次
edge2 -> 搜一次
...
edgeE -> 搜一次

并查集:
edge -> find + union

如果 E = 10000,逐边 DFS 可能重复扫描大量已有边;并查集基本只是在父指针数组上跳转,性能差距很明显。

五、和普通环检测的区别

普通无向图环检测常用 DFS,需要记录父节点,避免把来时边误判成环。冗余连接则利用题目“树加一条边”的特殊结构,只要发现一条边连接了已连通的两个端点,就能确定它是答案。

问题推荐方法关注点
无向图是否有环DFS 或并查集是否存在环
找树中多出来的边并查集哪条边首次连接同集合
有向图依赖成环DFS 三色或拓扑回边或入度剥离

面试时要讲清楚“无向边 + 动态加入 + 连通性查询”这三个信号,它们共同指向并查集。

六、复杂度与编号边界

路径压缩加按秩合并后,find/union 的均摊复杂度是 O(α(V)),几乎可以看成常数。处理 E 条边,总时间是 O(E α(V)),空间是 O(V)

题目常见节点编号从 1 到 n,所以数组开 n + 1。如果节点编号不连续,例如字符串账号或任意整数,就用哈希表把节点映射到连续编号,或者直接用 Map<T,T> 实现 parent。

七、常见误区与追问

记忆钩子:并查集不是在找路径细节,而是在问“这两个点之前是不是已经通了”;已经通了还加边,就是环。

  • 误区:看到环就一定用 DFS。 这题按边逐步加入,判断端点是否已连通更直接,并查集更贴合。
  • 误区:union 返回 true 时返回边。 union 成功表示两个集合原本不连通,这条边合法;union 失败才是冗余边。
  • 误区:数组只开到 edges.length。 如果节点编号从 1 开始,通常要开 n + 1,否则编号 n 会越界。
  • 追问:如果有多条冗余边怎么办? 经典题保证只有一条;若不保证,按题意可能返回第一条造成环的边,或返回最后一条,需要明确输入要求。
  • 追问:有向图冗余连接还能这样做吗? 有向图还涉及入度为 2 和有向环,需要额外分类讨论,不能直接套无向并查集。
  • 追问:为什么路径压缩有效? 每次 find 都把路径上的节点直接挂到根上,后续查询会越来越接近 O(1)。

八、加强记忆

冗余连接抓住三个词:无向图、树多一边、动态连通性。按输入顺序处理边,两个端点根不同就合并,根相同就说明已有路径,再加这条边会闭环。并查集让“是否已连通”变成近似常数查询,是这题比逐次 DFS 更优雅的原因。