路径压缩正确写法是递归版用return parent[x] = find(parent[x]),非递归版需两次遍历(先找根再改父指针);union前必须检查root_x != root_y,否则污染树高、误减连通分量数。

路径压缩在 find 中怎么写才不破坏并查集语义
路径压缩的本质是把查找路径上所有节点的父指针直接指向根,但必须确保「只改父指针,不改根本身的父指针」。常见错误是递归返回后误设 parent[root] = root 两次,或在非递归实现里漏掉最终赋值。
正确做法是:递归调用 find(parent[x]) 获取根,再将 parent[x] 设为该根——注意不是设为 parent[parent[x]] 或中间节点。
- 递归版必须有
return parent[x] = find(parent[x]),不能拆成两行(否则可能被优化掉或逻辑错位) - 非递归版需先遍历找根,再二次遍历改父指针;漏掉第二次遍历就等于没压缩
- 若用
std::vector<int></int>存父数组,下标从 0 开始时要确认输入节点编号是否也从 0 起,错位会导致越界或静默错误
union 操作里要不要检查是否同根再合并
必须检查。不检查就直接 parent[root_y] = root_x,会把两个本就联通的集合强行再连一次,虽不破坏正确性,但会污染树高、干扰后续按秩合并逻辑,还可能让路径压缩失效(比如形成环状引用,实际不会,但结构变差)。
更关键的是:很多题目要求统计连通分量数量,每次 union 前不判断是否已连通,就会多减一次计数。
立即学习“C++免费学习笔记(深入)”;
- 标准写法是
if (root_x != root_y) { parent[root_y] = root_x; count--; } - 如果启用了按秩合并(
rank数组),这里还要同步更新rank:仅当两秩相等时才给新根加 1 - 别用
find(x) == find(y)判断——这会触发两次路径压缩,开销白费;应直接比较root_x和root_y
按秩合并和路径压缩能一起用吗?性能影响有多大
能,而且推荐一起用。两者正交:路径压缩优化单次 find 的摊还时间,按秩合并控制树高增长。合用后 find 的摊还复杂度是反阿克曼函数 α(n),实践中可视为常数。
但要注意实现顺序:先做按秩合并(决定谁当新根),再在 find 里做路径压缩。反过来不行——压缩后的树高信息已失真,无法用于秩比较。
- 秩数组不必严格等于真实高度,只需保证「秩小的树挂到秩大的树下」即可;所以初始化全为 0 是安全的
- 若空间敏感,可用「大小合并」替代按秩合并(即维护子树节点数),但此时
union后要更新 size,且路径压缩不影响 size 统计 - 实测:对 1e6 次操作,纯路径压缩比「路径压缩+按秩合并」慢约 15%~20%,差距随数据规模增大而明显
为什么用 std::vector 而不用裸数组?哪些边界容易崩
用 std::vector 主要为自动管理内存和避免越界访问,但默认构造时不初始化元素值,vector<int> parent(n)</int> 是安全的(值初始化为 0),而 vector<int> parent(n, -1)</int> 才符合「-1 表示根」的习惯;若忘了初始化,parent[i] 是随机值,find 会立刻崩溃或死循环。
- 节点编号若从 1 开始,
parent数组大小应为n + 1,且parent[0]永远不用——别误把它当有效节点 - 合并时若传入非法索引(如
x >= n),vector::operator[]不检查,直接 UB;建议调试期加assert(x - 不要用
new int[n]配delete[]:异常安全差,且易忘释放;现代 C++ 就该用vector


















