并查集学习笔记
并查集学习笔记
基础并查集
P1551 亲戚
我们通过例题,来引入并查集。这道题我们很容易想到可以把互相认识的人扔到一个数据结构里。这就用到了并查集。
首先,并查集是考虑一个数组 $ fa $, $ fa_i $ 表示 $ i $ 的祖先。初始所有的 $ i $ 的祖先都是其自己。然后我们考虑合并。这样我们可以写出并查集初始化的代码。
inline void init () { for (register int i = 1; i <= n; ++ i) fa[i] = i; }
然后考虑合并。我们在合并的时候,只需要让其中一个人的祖先的祖先设置为另一个人的祖先就行了。
为什么不直接设一个人的祖先为另一个人呢?因为这样的话,一个人就从一个集跑到另一个集了。这样是不正确的,我们应该吧这两个集的人全部并到一个集里。
inline void merge (int x, int y) { int cx = find (x), cy = find (y); if (cx != cy) fa[cx]=cy; }
最后说一下查找,就一直查找祖先,然后祖先的祖先,祖先的祖先的祖先......直到找到祖先是自己即可。
inline int find (int x) { if (fa[x] == x) return x; else return find (fa[x]); }
然后套用模板就可以切掉这道题。
高级并查集
路径压缩
我们每次查找的复杂度有点高,所以我们考虑每次查找的时候直接将这个点最终的祖先存回 $ fa $ 数组即可。还是非常简单的。
inline int find (int x) { if (fa[x] == x) return x; else return fa[x] = find (fa[x]); }
考虑使用三目运算符压行。
inline int find (int x) { return fa[x] == x ? x : fa[x] = find (fa[x]); }
按秩合并
我们考虑到并查集是一棵树,那么如果我们把子节点多的树合并到少的树,要改变祖先的结点就会变多。那我们不妨把子节点少的树合并到子节点多的树,那么就可以起到优化的作用。
画个图看看就会发现,如果两个集子深度一样,合并就会使深度加 $ 1 $。如果深度不一样,深度小的不会产生影响。
下面是代码,首先是初始化:
inline void init () { for (int i = 1; i <= n; ++ i) fa[i] = i, height[i] = 1; }
然后是合并:
inline void merge (int x, int y) { int cx = find (x), cy = find (y); if (cx == cy) continue; if (height[x] == height[y]) fa[y] = x, height[x] += 1; else { if (height[x] > height[y]) fa[y] = x; else fa[x] = y; } }
后记
欢迎大家观看,我会继续更新其他的算法学习笔记。