ACM算法笔记(四)并查集

本文详细介绍了并查集这一数据结构,包括其作为集合合并与查询的两种基本操作。通过一个生动的故事来解释了合并(团队结盟)和查询(判断成员归属)的概念。并查集常用于求解无向图的连通分量、最小公共祖先等问题。文中给出了使用路径压缩优化的并查集实现,并提供了示例代码,展示了如何在实际问题中应用并查集解决亲戚关系查询问题。
摘要由CSDN通过智能技术生成

并查集

一个很友好 的数据结构(bushi)

并查集:(union-find sets)是一种简单的用途广泛的集合.
并查集是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作,应用很多,如其求无向图的连通分量个数、最小公共祖先、带限制的作业排序,还有最完美的应用:实现Kruskar算法求最小生成树(以后会学到)。

并查集的两种基本操作:

  • (1)合并;
  • (2)查询;

解释:对并查集概念的理解说白了就是一种合并集合的一个“算法思想”和结构方法,这是说并查集是一种方法,一个思路,也是一种数据结构,算是树形数据结构。

一个小故事对并查集的两种操作的形象化解释:

故事背景:
7777年的7月7日,有77个人被邀请去参加一个大赛,大赛官方设定规则,要求这77个人自由组队,争夺一项宝物,刚开始每一个人都是独立的个体,与谁组队全凭自愿,所以刚开始每个人都各自是一个单独的团队。

瞧这张图画的多好~~

(1)合并

有的势力力量过于强大,所以为了打败他们夺得宝物,有的团队就要进行结盟,合并团队。一个团队总要有一个首领,这个首领是每一个人的上级,如果把b和a合并,那么就要让a的所有人奉b的首领为首领,这样a和b两个团队的所有人的首领都是原来b的首领,这样就形成了一个新的团队了。这就是集合的合并。即a的首领的首领变为b的首领。

在这里插入图片描述
变为
在这里插入图片描述

(2)查询

现在有两个团队正在交战,为了争夺宝物,但是由于团队人数都特别多,有的人不能判断眼前这个人到底和自己是不是一个团队的,所以开始疑惑到底要不要“暴揍”这个人,为此,他们想出了一个点子,问一问对方的最大的首领是谁,如果发现对方最大的首领一样,那皆大欢喜,咱俩原来是一伙的!那就一块揍别人去;若果发现并不是一伙的,那就开始“干架”(bushi)


并查集的两个操作大致如此~上案例

例题:洛谷P1551亲戚题目

大致意思,现在给出若干个点,并且已知部分点具有相连关系(我们假设具有相连关系的一些点处于一个集合之中),要通过一系列操作进行集合的查找或者集合的合并,并得出结果。

算法核心思路:
——路径压缩,化多层树形结构为单层树形结构。

初始化: 每一个结点的上级都是自己,也就是说独立成一个集合 合并集合操作:

void merge(int x,int y)
{
    f[find(y)]=find(x);//让y的首领成为x的首领的小弟qwq
}

解释: f[i]指的是结点i的上一级结点的序号。
代码含义–>将y的上一级变为x的上一级的下级,也就是说让y的上一级成为x的同级(即y是x的下一级关系)
抛去上下级关系来看,y的最高级和x的最高级是同一个结点,这个样子可以知道,x和y算是一个集合之中。

查找操作:(含有路径压缩)

int find(int x)
{
    if(f[x]==x)
        return x;//说明这个x就是首领
    else
        return f[x]=find(f[x]);//路径压缩一波,让x的上一级首领变为x的现在上一级首领的上一级首领。
        //递归一下就成了x首领就是根节点首领了
}
注重解释路径压缩的部分(利用函数递归的思想,也就是栈结构) 假设现在已知结点1的上一级是结点3,节点3的上一级是结点5;即1->3->5 结点2的上一级是结点4;即2->4 现在我们想知道1和2是不是同一个集合关系。那么我们就要找1和2的最高级是不是一个结点对不对?也就是if(find(1)==find(2)) 那么就是同一个集合,反之就不是。

解释代码:
我们找find(1),很明显f[1]==3!=1,故find(1)返回f[1]=findf[1]这个时候我们注意到返回的不是一个值,是又一个函数,我们就需要进入到返回的那个函数find(3)去,一直直到返回是一个具体的值为止。
那么现在find(1)没有执行完毕,函数递归了,我们把find(1)压入栈中。现在开始执行find(3).很显然f[3]也是不等于3,应该返回f[3]=find(f[3])也就是返回f[3]=find(5);同样的,find(3)没执行完,压入栈中。接着执行find(5).这个时候好了,f[5]==5。那正好返回5。现在该把栈给弹出来了,最上面的是find(3),find(3)返回值是find(5)==5。也就是返回find(3)==5;接着弹出最后一个find(1).返回f[1]=find(3)==5。这样一个查找过程就完成了。所以我们知道结点1 的最高级是结点5。同样操作知道结点2的最高级是结点4,显然不在一个集合里面。

补充说明:在查找的过程中进行了路径压缩,所以这个过程不仅仅是得到了查找的内容,同时还优化改变了内容。仔细看,在这一个查找过程中得到了结点1的最高级是结点5,但是同时也使f[1]=5。这样直接把1的上级变成了5.在下一次操作如果还要搜索1的最高级是谁,那么直接可以知道f[1]==5,就不用再次重复上述那个递归函数的麻烦操作了,极大地简化了代码的复杂度。

图示:
在这里插入图片描述在这里插入图片描述
附上代码:

#include<iostream>
using namespace std;
const int maxn = 1000000;
int f[maxn]; 
int find(int x)
{
    if(f[x]==x)
        return x;//说明这个x就是首领
    else
        return f[x]=find(f[x]);//路径压缩一波,让x的上一级首领变为x的现在上一级首领的上一级首领。
        //递归一下就成了x首领就是根节点首领了
}·
void merge(int x,int y)
{
    f[find(y)]=find(x);//让y的首领称为x的首领的小弟qwq
}
int main()
{
    int n,m,k;
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)
        f[i]=i;
    for(int i=1;i<=m;i++)
    {
        int a,b;
        cin>>a>>b;
        merge(a,b);
    }
    for(int i=1;i<=k;i++)
    {
        int a,b;
        cin>>a>>b;
        if(find(a)==find(b))
            cout<<"Yes"<<endl;
        else 
            cout<<"No"<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值