路径压缩不能只写 parent[x] = find(parent[x]),因为c++求值顺序不保证先计算右边,必须显式两步:int root = find(parent[x]); parent[x] = root; return root;。

为什么路径压缩不能只写 parent[x] = find(parent[x])
这是初学者最常写的错误写法。表面上看递归调用后把父节点设为根,但漏掉了关键一步:必须在 find 返回前完成赋值,否则压缩失效。正确写法是先拿到根,再统一挂载——否则中间节点仍指向旧父节点,下次查询还是得走长链。
常见错误现象:find 调用后树高没明显下降,union 多次后仍出现 O(n) 查询;性能测试显示连通性判断变慢而非变快。
- 正确顺序:递归到底拿到根 → 回溯时逐层设置
parent[x] = root - 不能写成
return parent[x] = find(parent[x])(看似简洁,但 C++ 求值顺序不保证先算右边) - 推荐显式两步:
int root = find(parent[x]); parent[x] = root; return root;
union 里按秩合并(rank)和按大小合并(size)怎么选
路径压缩本身已大幅降低树高,但单独使用会导致 rank 信息失真(因为压缩后实际高度 ≠ rank 值)。所以实践中更推荐按大小合并:size 始终真实反映子树节点数,不受压缩影响,且能更好控制树的平衡性。
使用场景:需要频繁统计连通分量大小(比如图中最大团、岛屿面积),或对并查集最终结构有稳定性要求时,size 比 rank 更可靠。
- 按大小合并:比较
size[root_a]和size[root_b],小树根指向大树根,然后size[root_b] += size[root_a] - 按秩合并:仅用于控制深度上界,
rank不更新压缩带来的变化,适合纯连通性判断且内存敏感场景 - 二者可共存,但没必要;优先用
size,它顺便支持get_component_size(int x)查询
初始化与连通分量计数怎么保持同步
很多实现把初始连通分量数硬编码为 n,但一旦发生无效 union(如合并已连通的两点),计数就错。必须只在真正发生合并时才减一。
容易踩的坑:在 union(a, b) 里没判断 find(a) != find(b) 就直接执行合并逻辑,导致 count 被多减,后续 get_count() 返回负值或远小于实际值。
- 每次
union前必须检查是否已在同一集合:if (root_a == root_b) return false; - 只在此条件不成立时更新
parent、size和count - 如果需要支持撤销(如离线查询),计数就不能用单变量,但本场景不需要
C++ 实现要注意的内存与内联细节
标准实现里 find 是递归函数,小数据量没问题,但节点超 10⁵ 时可能栈溢出。生产环境建议改用迭代版;另外所有热路径函数(find、union)应加 inline,避免虚函数或动态调度开销。
性能影响明显:未内联的 find 在高频调用下(如 Kruskal 算法中排序后遍历边)会多出 15%+ 的函数调用成本;而迭代版虽代码稍长,但无栈风险且 CPU 分支预测更友好。
- 迭代
find必须做两次遍历:第一次找根,第二次路径压缩(不能一边找一边压,否则丢根) - 用
std::vector存parent和size,避免new[]手动管理(易泄漏且不 cache-friendly) - 构造函数中用
resize(n)并配合iota(parent.begin(), parent.end(), 0)初始化,比循环更快
union 的守卫判断和 find 的赋值时机里。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











