路径压缩在 find 中需递归更新:先获取根节点,再将当前节点 parent 设为该根,确保所有同集合节点指向同一根;迭代实现须两遍遍历,否则断链。

路径压缩在 find 中怎么写才不破坏并查集语义
路径压缩的核心是让查找过程中经过的所有节点直接指向根,但必须确保不改变任意节点的逻辑归属关系。常见错误是只改了当前节点的 parent,却没处理其子树——这会导致后续 find 返回错误根节点。
正确做法是在递归回溯时更新:先拿到根,再把当前节点的 parent 设为该根。这样既压缩了路径,又保持了“所有同属一集合的节点最终都指向同一个根”的不变性。
-
find必须用递归或显式栈实现;迭代写法若只改当前节点而跳过中间层,压缩不彻底 - 不要在
union里调用未压缩的find做判断,否则压缩失效——两次find可能返回不同中间父节点 - 如果用迭代 + 路径压缩,需先遍历到根,再反向遍历一遍改父指针,否则会断链
带按秩合并的 union 为什么比单纯路径压缩更稳
只做路径压缩时,树高虽平均降低,但最坏单次 find 仍可能退化成 O(n)(比如反复 union 末尾节点)。按秩合并(union by rank/size)限制树的生长方向,配合路径压缩后,单次操作均摊时间才能稳定在接近 O(α(n))。
注意:rank 不是真实树高(压缩后树高已失真),只是启发式指标;用 size 更直观,但需额外维护数组。
- 合并时永远把小树根挂到大树根下,
size[a] >= size[b]→parent[b] = a - 更新
size[a]时只加size[b],别加 1(b本身已计入) - 如果同时用
rank和路径压缩,rank只在两棵树 rank 相等时才对根rank++,其余情况不更新
初始化和边界场景下容易漏掉的检查点
并查集常被用于图连通性、动态连通、最小生成树 Kruskal 等场景,但初始化不当或忽略边界,会导致判定逻辑全错。
典型问题:节点编号从 1 开始但数组开成 parent[n],访问 parent[n] 越界;或未初始化 parent[i] = i 就直接 find,读到随机值。
- 务必在构造函数中循环设置
parent[i] = i和size[i] = 1(或rank[i] = 0) - 输入边含重复、自环(
u == v)时,union(u, v)应直接跳过,避免误改size或引发无意义递归 - 判定两个节点是否连通,必须用
find(u) == find(v),不能直接比parent[u] == parent[v]——压缩后父节点未必是根
一个轻量但可靠的 C++ 实现参考
以下代码满足:支持 0-based 节点索引、按 size 合并、路径压缩、线程不安全但无隐藏状态泄漏。关键处已加注释说明易错位。
class UnionFind {
std::vector<int> parent, size;
public:
UnionFind(int n) : parent(n), size(n, 1) {
for (int i = 0; i <p>实际使用时,<code>unite</code> 返回 <code>bool</code> 表示是否发生了合并,这对 Kruskal 判定 MST 边数是否达标很关键。如果忽略返回值,可能多加一条冗余边而不自知。</p></int>C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











