并查集的常见用法

作用

  1. 将两个集合合并
  2. 询问两个元素是否在一个集合当中

基本原理

每个集合用一颗树来表示。树根的编号就是整个集合的编号。每个节点储存他的父亲节点,p[x]表示x的父节点

p[x]就表示x属于哪个集合。

问题

  1. 如何判断树根(祖宗集合) :if ( p[x] == x );
  2. 如何求x的集合编号 : while ( p[x] != x ) x = p[x];
  3. 如何合并两个集合 :p[x] 是 x 的集合编号,p[y] 是 y 的集合编号 ,让p[x] = y;

最最最重要

递归的含义就相当于你问你爸爸你的祖先是谁,你爸爸也不知道,爸爸就去问爷爷,然后你的爷爷也不知道,爷爷就去问你的太爷爷,你的太爷爷年纪太大了,啥也不记得,就去问你的祖先

这个时候注意,你的祖先是知道自己是谁的,所以x ==p[x]

p[x]中储存的是每个节点的父节点,一开始并不是树状的,而是一个个单独的节点,每个节点都是根节点,都有集合编号,然后通过不断的合并才形成的树状结构

这个函数是并查集中最重要的部分,背过理解

int find(int x)
    {
        if (p[x] != x) p[x] = find(p[x]); //路径压缩
        return p[x];
    }

模板

(1)朴素并查集:

朴素并查集:

    int p[N]; //存储每个点的祖宗节点

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ ) p[i] = i;

    // 合并a和b所在的两个集合:
    p[find(a)] = find(b);

(2)维护size的并查集:

size只有祖节点的有意义

要特别注意所有处理size的地方,都要“归根结底”

维护size的并查集:

    int p[N], size[N];
    //p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        size[i] = 1;
    }

    // 合并a和b所在的两个集合:
    size[find(b)] += size[find(a)];
    p[find(a)] = find(b);

(3)维护到祖宗节点距离的并查集:

维护到祖宗节点距离的并查集:

    int p[N], d[N];
    //p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x)
        {
            int u = find(p[x]);
            d[x] += d[p[x]];
            p[x] = u;
        }
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        d[i] = 0;
    }

    // 合并a和b所在的两个集合:
    p[find(a)] = find(b);
    d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量

合并集合

题目描述:

p[x]就表示x属于哪个集合。

code:

int find(int x)
{
    if(p[x]!=x) // 去找祖宗
    {
        int t = find(p[x]); // 找到祖宗
        p[x] = t; // 祖宗秒变爹   路径压缩 
    }
    return p[x];  //返回的是节点的祖宗 
}
int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++) p[i]=i;
    while (m -- )
    {
        char c;
        int a,b;
        cin>>c>>a>>b;
        if(c=='M') 
        {
            p[find(a)]=find(b); //让a的祖宗都是b的,也就是说,让a的整个集合都属于b; 
        }
        else 
        {
            if(find(a)==find(b)) puts("Yes");
            else puts("No");
        }
    }
    return 0;
}
null
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示