find函数必须分两步取根再赋值,直接写parent[x] = find(parent[x])因求值顺序未定义而错误;union前须用find(u)==find(v)判连通,初始化需vector parent(n+1)对齐下标。

find 函数必须显式分两步:先取根再赋值
直接写 parent[x] = find(parent[x]) 是错的——C++ 不保证赋值表达式右侧先求值,编译器可能先读旧 parent[x]、再调 find()、最后才写回,导致中间节点根本没被更新。实测中树高几乎不变,多次查询退化到 O(n)。
正确做法只有两种等价形式:
int root = find(parent[x]); parent[x] = root; return root;-
return parent[x] = find(parent[x]);(注意:这是单条语句,return和=组合在语法上强制右侧先完成全部递归和赋值)
别用 find(parent[x]); parent[x] = root; 这种拆法——root 未定义,且第一行调用后没保存返回值。
union 操作前必须用 find(u) == find(v) 判连通,不能跳过
漏掉这一步会导致:count 多减、size 或 rank 错误更新、树高意外增加。尤其处理重边或邻接表时,同一条边反复输入,不判就会让本已扁平的结构重新变深。
关键细节:
- 必须用
find(u) == find(v),不是parent[u] == parent[v]——后者在路径未完全压缩时不可靠,常返回假阴性 - 比较完立刻用两个根做合并,别再调一次
find(),避免重复压缩开销 - 按大小合并时,只在
root_u != root_v时执行size[root_v] += size[root_u]并更新父指针
初始化时 vector 长度和下标偏移必须对齐
节点编号是 1 到 n,却声明 vector<int> parent(n)</int>,然后访问 parent[1],必然越界。更隐蔽的问题是:声明了 parent(n + 1) 却只初始化 i = 0 到 n - 1,导致 parent[n] 为 0(默认值),后续 find(n) 直接指向 parent[0],引发静默错误或虚假连通。
安全写法只有一种:
vector<int> parent(n + 1), size(n + 1, 1);</int>for (int i = 1; i
如果输入含节点 0,且你确定编号连续,则可用 parent(n) + for (int i = 0; i ,但务必全程保持一致,别混用。
迭代版 find 更适合生产环境,但必须两遍扫描
递归版在链长超 1e5 时容易栈溢出;迭代版可控,但常见错误是只做一遍找根,漏掉第二遍路径重连——结果只是返回了根,沿途节点的 parent 指针一个没改,等于没压缩。
正确迭代逻辑分两段:
- 第一遍:用
while (root != parent[root]) root = parent[root];找到根 - 第二遍:用
while (x != root)循环,每次保存next = parent[x],再设parent[x] = root,最后x = next
少任何一段,路径压缩就不彻底;临时变量 next 不可省,否则会丢失原父节点导致路径断裂。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











