目录
个人介绍
各位CSDN的友友们,大家好,我是小熙,一个算法小透明,希望在CSDN的大舞台可以和大家一起取得进步(^_^)
个人现状:上个学期在学校的ACM小组学了一个学期的算法,但是世界很大,我的梦想很远,我希望在我年轻的时候可以走出国门,到世界的其他地方看一看,再加上我的算法功底可能真的不太足以支持我在ACM比赛上做出成绩。所以我选择逐渐淡出ACM小组,并把之前的知识整理成博客供大家分享。
学习算法的小方法:知道算法的基本原理——>知道代码实现——>总结出代码模板并熟练于心——>基本模板题——>变式思维题
并查集的概况
个人很喜欢ACM区域赛金牌大神刁鹏杰学长在给我们讲课的时候的一句名言:你们要知道,数据结构是用来用的,光知道实现方式而不用——假把式罢了。
下面,我将从概念介绍,用途,代码实现方式,模板和基本例题五个角度来分享并查集这一数据结构。
并查集的概念
概念介绍:
并查集:一种树形的数据结构。
概念阐述:
(1)何为树形,比如说一个公司里面有编号为“1”“2”“3”“4”“5”的五个员工,“1”是“2”“3”“5”的上司,“2”又是是“4”上司,那么知道了这些关系,便可以绘制出一张树形图,下面这张图片就可以表示这种关系了。
(2)注意的问题,判断一个数据集合是不是树形结构关键是看元素之间是否存在某种关系。而这种关系在题目中经常体现为“长幼”“上下级”关系,比如“A是B的长辈”,“A是B的上司”
并查集的用途
用途:处理集合与集合间的关系并查询元素与集合之间的关系
(1)合并:将两个小的集合合并成为一个大的集合。从图的角度是指合并连通块。
(2)查找:查找某个元素隶属于那一个集合。从图的角度是某个元素隶属于哪个连通块。
(2)注意的问题,并查集通常用来处理元素之间具有传递性的集合问题,只要看到题干里面有“大小”“血缘”“上下级”的关系就要想到并查集,这个大家在看本文后面的题目的时候就会感受到了。
并查集代码的实现原理与方式
初始化
需要开的变量:f[N]数组,f[i]表示的是下标元素i的祖先是哪一个元素
初始化:
int f[10000]; for(int i=1;i<=n;i++) { f[i]=i;//表示的是i元素的祖先是自己,因为这个时候还没有进行并的操作,祖先只可以是自己。 }
查询
文字原理:用递归函数查找元素i的祖先结点,找到以后进行路径压缩。
int getf(int x)//目的是找出当前结点的祖先 { if(f[i]==i) return i;//如果当前结点的父亲是它本身,就返回当前结点 else { f[i]=getf(f[i]);//用递归的思想,写递归函数询问当前结点父亲的祖先 return f[i]; } }
上面的代码是不是又臭又长,来,我们简化一下。
int getf(int x) { return f[x]==x?x:f[x]=getf(f[x]); }
合并
文字原理:
(1)上面我们已经知道怎么查询祖先了,那么我们现在就来看合并吧。你想想既然要合并集合A和B,那么是不是只要在A中选取一个编号为“x”的元素,在B中选取一个编号为“y”的元素,再架一座“桥”相连接就可以了。
(2)在同一连通块中的每一个元素他们的祖先结点都是相同的。不同的话就不是同一连通块了,直接并上去。
void union(int x,int y) { int fx=getf(x);//fx表示的是x的祖先 int fy=getf(y);//fy表示的是y的祖先 if(fx!=fy)//如果祖先不相同的话就代表不是一个连通块,合并 { f[fx]=fy;//合并的操作 } //祖先相同就没有合并的必要了 }
并查集的代码模板
代码模板:
引用一位大牛的话——代码模板好比数学公式,一定要熟练敲上几十遍,考试考的是模板的变式与思维。
初始化
int f[10000]; for(int i=1;i<=n;i++) { f[i]=i;//表示的是i元素的祖先是自己,因为这个时候还没有进行并的操作,祖先只可以是自己。 }
查询
int getf(int x) { return f[x]==x?x:f[x]=getf(f[x]); }
合并
void union(int x,int y) { int fx=getf(x);//fx表示的是x的祖先 int fy=getf(y);//fy表示的是y的祖先 if(fx!=fy)//如果祖先不相同的话就代表不是一个连通块,合并 { f[fx]=fy;//合并的操作 } //祖先相同就没有合并的必要了 }
练习题
例题: P3367 【模板】并查集 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int f[N];
int getf(int x)//查询的模板
{
return f[x]==x?x:f[x]=getf(f[x]);
}
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) f[i]=i;//初始化的模板
for(int i=1;i<=m;i++)
{
int z,x,y;
cin>>z>>x>>y;
int fx=getf(x);
int fy=getf(y);
if(z==1)
{
if(fx!=fy)
{
f[fx]=fy;//并的模板
}
}
if(z==2)
{
if(fx==fy)
{
cout<<"Y"<<endl;
}
else
{
cout<<"N"<<endl;
}
}
}
return 0;
}
分析:这题基本就是模板公式的堆砌,没什么好说的。
P1551 亲戚 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include<bits/stdc++.h>
using namespace std;
const int N=5010;
int f[N];
int getf(int x)
{
return f[x]==x?x:f[x]=getf(f[x]);//查询的模板
}
int main()
{
int n,m,p;
cin>>n>>m>>p;
for(int i=1;i<=n;i++) f[i]=i;
for(int i=1;i<=m;i++)
{
int x,y;
cin>>x>>y;
int fx=getf(x);
int fy=getf(y);
f[fx]=fy;//并的模板
}
for(int i=1;i<=p;i++)
{
int x,y;
cin>>x>>y;
int fx=getf(x);
int fy=getf(y);
if(fx==fy) cout<<"Yes"<<endl;
else
cout<<"No"<<endl;//查询的模板
}
return 0;
}
分析:在并查集的用途那里讲了,只要看到题目里面有“大小”“血缘”“上下级”之类的可传递关系就要想到并查集,这里是“血缘关系”,所以直接用并查集
P1536 村村通 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int f[N];
int getf(int x)
{
return f[x]==x?x:f[x]=getf(f[x]);//查的模板
}
int main()
{
while(1)
{
int ans=0;
int n,m;cin>>n;
if(n==0)
{
return 0;
}
cin>>m;
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++) f[i]=i;//初始化的模板
for(int i=1;i<=m;i++)
{
int x,y;cin>>x>>y;
int fx=getf(x);
int fy=getf(y);
if(fx!=fy) f[fx]=fy; //并的模板
}
for(int i=1;i<=n;i++)
{
if(f[i]==i) ans++;//统计独立的连通块的数量
}
cout<<ans-1<<endl;
}
return 0;
}
分析:题目要求修建道路的数目,那么就转化为求联通块的个数,道路数=联通块数量-1
总结:并查集还有一种用途,统计彼此之间互相独立的联通块的数量。只需要用for循环遍历每一个元素中满足f[i]==i的情况出现的数量即可(i是指当前的元素)