并查集学习笔记

并查集学习笔记

基础并查集

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;

    }

}

后记

欢迎大家观看,我会继续更新其他的算法学习笔记。

posted @ 2023-01-18 21:21  __Tzf  阅读(12)  评论(0编辑  收藏  举报