并查集:擒贼先擒王

定义

并查集,在一些有\(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\n");
            else
                printf("N\n");
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/lyfoi/p/9479832.html
今日推荐