路径压缩必须显式分两步:int root = find(parent[x]); parent[x] = root; return root;因C++不保证return parent[x] = find(parent[x])中右侧先求值,否则压缩失效、树高不降、查询退化为O(n)。

路径压缩必须分两步写,不能用 return parent[x] = find(parent[x])
这是 C++ 实现中最容易出错的点。表面上看这行代码既递归又赋值,但 C++ 标准不保证 find(parent[x]) 先求值——编译器可能先取 parent[x] 地址再调用 find,导致未压缩就返回旧值。
- 错误现象:
find调用后树高几乎不变,多次union后查询仍退化到O(n) - 正确写法必须显式拆成两步:
int root = find(parent[x]); parent[x] = root; return root; - 迭代版更安全(尤其节点数 > 1e5):先循环找根,再第二遍遍历改父指针
按大小合并(union-by-size)比按秩合并(union-by-rank)更实用
rank 是对高度的近似,路径压缩后它就失真了;而 size 始终精确反映子树节点数,且天然支持 get_size(int x) 查询。
- 合并逻辑:比较
size[root_a]和size[root_b],小树根指向大树根,然后size[root_b] += size[root_a] - 不用同时维护
rank和size——二者目标重叠,徒增维护成本 - 如果只做连通性判断且内存极度敏感,才考虑纯
rank;否则默认用size
unionSet 必须先 find 再判等,不能直接操作原始输入
直接 unionSet(a, b) 就改 parent[a] 或 parent[b],等于把非根节点当根处理,结构瞬间崩坏。
- 正确流程:先
root_a = find(a)、root_b = find(b),再判断if (root_a == root_b) return; - 连通分量计数
count只在真正合并时减一;漏掉判等会导致count变负或严重偏低 - 函数名别用
union——它是 C++ 关键字;推荐unionSet或merge
容易被忽略的边界和调试陷阱
实际编码时最常踩的坑不在算法逻辑,而在细节处理:
立即学习“C++免费学习笔记(深入)”;
-
parent和size数组必须初始化为vector<int>(n)</int>,不能依赖默认构造(否则访问越界) - 稀疏编号(如 ID 不连续)别硬套
vector下标——该用unordered_map就用,别为了“省空间”引入哈希冲突或查找开销 - 所有热路径函数(
find、unionSet)加inline,避免函数调用开销在高频场景下放大


















