但裸的并查集还不够好。真正让它“几乎等于 O(1)” 的,是 path compression 和 union by rank/size。path compression 的意思是:你查一次根,就顺手把沿途节点直接挂到根上,下次再查就更短。union by size 的意思是:小树挂大树,别反过来。为什么这样设计?因为所有性能问题本质上都来自树太高。你不控制高度,find 就会退化成一长串父指针追踪,最后跟链表没区别。
这里最容易踩坑的地方,不是原理,而是实现细节。第一种坑是把 union(x, y) 写成 parent[x] = y,这几乎总是错的,因为 x 和 y 可能都不是根,你是在随手改中间节点,结构会变脏。正确做法是先找 rootX 和 rootY,再决定谁挂谁。第二种坑是以为 path compression 会改变“集合内容”,其实不会,它改的是树形结构,不改连通关系。第三种坑是把并查集拿去做删除边、撤销合并这类操作;这就超出它的舒适区了,因为它擅长的是只增不减的连通性维护,不擅长回滚和动态拆分。