并查集:擒贼先擒王

Posted lyfoi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并查集:擒贼先擒王相关的知识,希望对你有一定的参考价值。

定义

并查集,在一些有(N)个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间((1)~(3)秒)内计算出试题需要的结果,只能用并查集来描述。摘自百度百科。

实现

(A,B,C,D)四个人,他们各自为王,一天:

(A)(B)打架了,然后(A)战胜了(B),于是(B)成了(A)的小弟,于是数组(F(B)=A),代表(B)的老大是(A)

然后(C)(D)打架了,(C)打败了(D)(D)成了(C)的手下,于是数组(F(D)=C),代表(D)的老大是(C)

下面,(C)的手下D与A打架了,(A)战胜了(D),于是(D)要归顺(A)

(C)一看就不干了,这是我的人啊,不过后来为王,于是(A)不但收服(D),还然(C)成为了他的小弟,于是数组(F(C)=A),代表(C)的老大是(A)

大概是如上的,这个过程我们用语言描述为:

并查集通过一个一维数组来实现,本质上是维护一个森林。刚开始的时候,森林里的每一个结点都是一个集合(也就是只有一个结点的树,是孤立的),之后根据题意,逐渐将一个个集合合并(也就是合并成一棵大树)。之后寻找时不断查找父节点,当查找到父结点为本身的结点时,这个结点就是祖宗结点。合并则是寻找这两个结点的祖宗结点,如果这两个结点不相同,则将其中右边的集合作为左边集合的子集(即靠左,靠右也是同一原理)。

所以说我们要一个找老大的函数,通过递归实现

int find(int k){
    if(f[k]==k)return k;//如果找到了老大
    return find(f[k]);//否则继续找
}

上面的代码不是很靠谱,可能会(TLE),所以要用到路径压缩。路径压缩就是当(B)归顺A前,(C)还是(B)的老大,那么我们不仅要(F(D)=A),还要(F(F)=A),把所有节点的祖先直接定为最高的祖先,就是路径压缩。代码非常的简单:

int find(int k){
    if(f[k]==k)return k;
    return f[k]=find(f[k]);
}

下面就简单了,把每个人的老大设置为自己:

 for(i=1;i<=n;i++)
        f[i]=i;

判断两个人是否是一个老大:

if(find(a)==find(b))

让一个人臣服另一个人:

f[find(B)]=find(A);//B打赢了A,于是让A的一切关系归属B的老大

现在你基本学会了并查集了,那么让我们做一道板子题目:

例题

洛谷 P3367 【模板】并查集

Code

#include<bits/stdc++.h>
using namespace std;
int n,m,f[10010];
int find(int k)
{
    if(f[k]==k)return k;
    return f[k]=find(f[k]);
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        f[i]=i;
    for(int i=1;i<=m;i++)
    {
        int x,A,B;
        cin>>x>>A>>B;
        if(x==1)
            f[find(B)]=find(A);
        else
            if(find(A)==find(B))
                printf("Y
");
            else
                printf("N
");
    }
    return 0;
}

以上是关于并查集:擒贼先擒王的主要内容,如果未能解决你的问题,请参考以下文章

关于并查集的一切全在这里了

想要学会并查集吗?看我四十行代码实现它

树--12---并查集

笔记并查集---无向图处理代码模板及类型题

并查集

力扣 每日一题 886. 可能的二分法难度:中等,rating: 1794(并查集 / 拆点优化的扩展域并查集)