不写路径压缩会导致find退化为O(n),10⁵次操作易超时;路径压缩使后续查询趋近O(1),且必须在find中递归实现并正确处理返回值;初始化需统一0-indexed,读入后立即减1,parent[i]严格设为i。

为什么 find 不写路径压缩,unionSet 就容易超时?
朋友圈问题本质是动态连通性判断:每次合并两人所属集合,最后统计剩余连通分量个数。若 find 只做朴素递归(不压缩),树高可能退化成 O(n),单次查询变成线性;10⁵ 次操作就可能超时。路径压缩把「查完即平」——每次 find(x) 都让 x 直接连到根,后续再查就是 O(1)。
关键不是“加了压缩就快”,而是“不加压缩,unionSet 的合并逻辑会反复触发深链遍历”。比如按秩合并(rank)没配路径压缩,照样可能长出高树。
-
find必须用递归或显式栈实现路径压缩,迭代写法容易漏压中间节点 - 压缩时机只在
find返回前,不是在unionSet里手动改父指针 - 不要在
find中直接写parent[x] = find(parent[x])而不处理返回值——这会导致未定义行为(C++中未初始化引用或悬垂指针)
parent 数组初始化和索引边界怎么踩坑?
朋友圈人数 n 给的是编号 1~n,但 C++ 数组下标从 0 开始。若直接开 vector<int> parent(n)</int>,访问 parent[n-1] 对应第 n 人,但输入的 a、b 是 1-indexed,不减 1 就越界。
更隐蔽的坑是:题目说“初始每人自成一圈”,意味着 parent[i] = i。但如果初始化写成 parent[i] = i+1 或漏设某个下标,后续 find 会跳到非法地址,轻则答案错,重则段错误。
立即学习“C++免费学习笔记(深入)”;
- 统一用 0-indexed:读入 a,b 后立刻执行
a--; b--; - 初始化循环必须覆盖全部 n 个位置:
for (int i = 0; i - 如果输入含 0 编号(极少见),要确认题干是否真允许——多数 OJ 的“1~n”是严格约束
路径压缩 + 按秩合并,rank 数组到底存什么?
rank 不是树的实际高度(压缩后高度失真),而是“该根节点作为合并主导方时,预估的树高上限”。它只在 unionSet 中用于决定谁当新根:谁 rank 大,谁当根;相等时才让一方当根并 rank++。
常见误写是把 rank 当作当前子树高度去更新——这是错的。压缩不改变 rank 值,因为 rank 只参与合并决策,不参与查找。
-
rank初始全 0(单点树,高度为 1,但约定 rank=0 表示“未合并过的根”) - 只有当两子树
rank相等时,新根的rank才加 1;否则不更新 - 不要在
find里碰rank数组——它和路径压缩完全无关
朋友圈最终人数怎么数?别数 parent[i] == i 就完事
路径压缩后,所有节点父指针都指向根,所以连通分量数 = 根节点数量 = 满足 parent[i] == i 的 i 的个数。但前提是:你调用了所有 find 强制压缩过。
如果只合并没查,部分节点父指针仍指向中间节点(比如 a→b→c,c 是根,但 a 的 parent 还是 b),此时 parent[a] != a,parent[b] != b,parent[c] == c,看起来是 1 个根——但若中途没对 a、b 调 find,它们没被压,这个计数依然正确。不过保险起见,标准做法是:合并完,对每个 i 调一遍 find(i) 再统计。
- 最简方案:合并结束后,循环执行
find(i),再统计parent[i] == i - 不要用 set 存根再 size()——多一次哈希开销,且没解决未压缩问题
- 如果题目要求输出每个圈子大小,得用 map 统计每个根的子节点数,这时必须先全量
find确保父指针干净
路径压缩真正难的不是代码几行,是理解“压缩只影响查找路径,不影响合并逻辑;rank 只服务合并,不参与查找”。这两个职责一旦混淆,调试时就会在 find 里改 rank,或在 unionSet 里手动压路径——结果既慢又错。


















