并查集和带权并查集

并查集

简介

并查集是一种以数组为存储结构的树形数据结构,当其中一些元素发生从属关系时,我们把可以关联的元素看做一个个的集合(即A连接B,C也连接B,那么A,B,C可以看做一个集合)

基本操作

初始化

首先定义一个father数组,初始化就是令数组中每个人的父亲都是他自己,即f[i]暂时表示i的父亲。当出现这种情况时,它就是这一脉的祖宗

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

建立关系

建立关系的步骤就是对于每给出的一对数x和y,我们都假设成x是y的祖先(或者都假设成y都是x的祖先)

father[y]=x;

然后这样就能连成树状结构

在这里插入图片描述

但是这样还不行,当我想找两个数是否是不是同一祖先,我必须顺着树形结构依次摸到公共祖先(摸的方法是递归查找,当且仅当一个数的祖先是它的本身返回给该值),显然时间是不够的,比如下面的函数就是求一个数x的最大祖先

int Find(int x){
    if(father[x]!=x)
        return Find(father[x]);
    return x;
}

因此我们引入路径压缩的概念:假如6已经是4的祖先,那么我们新增一个关系是"4 7",则我们,可以直接在递归查找的过程中,将7的father直接设为6,这样可以避免链状树形结构的出现而直接形成最多深度为2的树。

在这里插入图片描述

Find函数

int Find(int x){
    if(father[x]!=x)
        return father[x]=Find(father[x]);
    return x;
}

//或者写成最简短的形式
int Find(int x){
    return father[x]==x?x:father[x]=Find(father[x]);
}

Union函数

如果我们需要把x和y两个不同的集合连接,显然需要找出他们的fx和fy,然后将fx连向fy或者fy连向fx都可

void Union(int x,int y){
    int fx=Find(x);
    int fy=FInd(y);
    if(fx!=fy){
        father[fy]=fx;//令y的祖先变成x的祖先
    }
}

计算集合的个数

不管是实际生活还是做题,使用并查集计算某个群体中集合的个数是并查集最直接的应用,在上面我们联合函数的基础上,我们是否能通过集合去重实现求集合个数呢?答案是否定的,有的同学会想,我们不是把所有有联系的群体都设置为公共的祖先了吗?

在Union函数的过程中,当要出现大于等于三个人的关系时,Find函数就可以轻松将第三层关系转化到第二层的关系上,但是可能其他的二层关系并没有接入到该集合的公共祖先上,主要是操作的先后顺序影响

在这里插入图片描述

方法一:不管上述瑕疵直接求

int ans=0;
for(int i=1;i<=num;i++)
    if(father[i]==i) ans++;

方法二:显然我们把所有的数再都执行一遍Find()这样就能把相关的关系连接到一起(如上图如果再执行一次Find(5)就可以把该集合中所有元素连接到1节点上)

int ans=0;
for(int i=1;i<=num;i++)
    Find(i);
for(int i=1;i<=num;i++)
    if(father[i]==i) ans++;
带权并查集

想必学过数据结构(或者离散数学)的人都知道一种特殊的图叫做带权图,当然也有带权的树。因此我们就不难明白带权并查集是什么了。我们在维护集合的联系关系时,还要维护一个可以用值表现的关系(如对立,距离等),下面我们都用一个数组value表示这种权值关系,注意每次value数组要清零,可以和father数组的初始化一起进行

在这里插入图片描述

有了上面并查集的基础,废话不多说,直接解释Find函数和Union函数:

Find函数

显然当出现新的联系时,我们在考虑路径压缩的同时,还应保存下来这次father查到的数(假设与X不相同),使value加上到祖先上的value之和

int Find(int a)
{
    if(a == father[a]) return a;
    int t = father[a];
    father[a] = Find(father[a]);
    value[a] = value[a]+value[t]; //注意递归过程的value求和的奥妙
    return father[a];
}

Union函数

当出现新的联系时,如下图的两个集合,我们需要如果我们要把y的集合连向x的集合,显然需要father[fy] = fx;

在这里插入图片描述

接下来value的变化和我们连接祖先节点应该照应。带权并查集根本上是带权有向图,这里的指向关系是一种向量关系。因此我们更新value[fy],并且从fy指向fx的权值应该是通过y与x的关系倒着求出来(注意向量的求和)

如果是x集合连向y集合就反过来

void Union(int x, int y, int s)
{
    int fx = Find(x);
    int fy = Find(y);
    if(fx!=fy){
        father[fy] = fx;
        value[fy] = value[x]-value[y]+s;
    }

}
  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值