并查集学习

转自:https://zhuanlan.zhihu.com/p/93647900,写的太好,膜拜

1.解决问题

管理不相交的集合,解决元素分组问题。包括两种操作:

  • 合并(Union):把两个不相交的集合合并为一个集合。
  • 查询(Find):查询两个元素是否在同一个集合中。

2.实现

//具体的理解过程上述连接中有,很生动

初始化:

int fa[MAXN];
inline void init(int n)
{
    for (int i = 1; i <= n; ++i)
        fa[i] = i;
}

 每个节点单独成集合,父节点都是自己。

 查询所在集合中的根节点:

int find(int x)
{
    if(fa[x] == x)
        return x;
    else
        return find(fa[x]);
}

递归查询,根节点的父节点是自身,查询的目的就是找到根节点,如果当前不是,那么就递归找当前节点父节点的根节点。

合并集合: 

inline void merge(int i, int j)
{
    fa[find(i)] = find(j);
}

本函数是将i所在的集合合并到j。

3.路径压缩

上面中存在的问题是树的深度会很深,那么在查询的时候进行压缩,查询到集合所在的根节点之后,直接将当前节点的父节点设置为根节点,减少下次查询的路径长度。希望每个元素到根节点的路径长度尽可能短。

查询(带路径压缩):

int find(int x)
{
    if(x == fa[x])
        return x;
    else{
        fa[x] = find(fa[x]);  //父节点设为根节点
        return fa[x];         //返回父节点
    }
}

简写:

int find(int x)
{
    return x == fa[x] ? x : (fa[x] = find(fa[x]));
}

//太牛了吧。

4.改进:按秩合并

记录每个节点所在的树的深度,在合并时将深度较小的合并到深度较大的集合中。

5.类(按节点个数合并)

参考力扣1631. 最小体力消耗路径官方题解提供的代码:

// 并查集模板
class UnionFind {
public:
    vector<int> parent;
    vector<int> size;
    int n;
    // 当前连通分量数目
    int setCount;
    
public:
    UnionFind(int _n): n(_n), setCount(_n), parent(_n), size(_n, 1) {
        iota(parent.begin(), parent.end(), 0);
    }
    
    int findset(int x) {
        return parent[x] == x ? x : parent[x] = findset(parent[x]);
    }
    
    bool unite(int x, int y) {
        x = findset(x);
        y = findset(y);
        if (x == y) {
            return false;
        }
        if (size[x] < size[y]) {
            swap(x, y);
        }
        parent[y] = x;//将节点少的合并到节点多的连通分量上
        size[x] += size[y];
        --setCount;//合并了之后就减少一个连通分量
        return true;
    }
    
    bool connected(int x, int y) {
        x = findset(x);
        y = findset(y);
        return x == y;
    }
};
//操作语句
UnionFind uf(m * n);//初始化m*n大小的并查集
uf.unite(x, y);//合并两个节点
原文地址:https://www.cnblogs.com/BlueBlueSea/p/14241195.html