并查集
简介
并查集是一种以数组为存储结构的树形数据结构,当其中一些元素发生从属关系时,我们把可以关联的元素看做一个个的集合(即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;
}
}