一.基础概念
并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)。比如说,我们可以用并查集来判断一个森林中有几棵树、某个节点是否属于某棵树等。
二.并查集的四大特点
1.每一个元素都对应一个结点;
2.每一组数据中的多个数据都在同一棵树中;
3.一组中的数据对应的树和另外一组中的数据对应的树之间没有任何联系;
4.元素在树中并没有子父集关系的硬性要求。
三.并查集体现的算法思想
用元素中的某个元素来代表这个集合,该元素成为集合的代表元;一个集合内的所有元素组织成以代表元为根的树形结构。
对于每一个元素fa[x]指向x在树形结构上的父亲结点。如果x是根节点,则令fa[x]=x。
对于查找操作,假设需要确定x所在的集合,也就是确定集合的代表元。可以沿着fa[x]不断在树形结构中向上移动,直到到达根节点。
四.如何创建一个并查集
创建并查集只需要三个步骤:
1.初始化。把每个点所在集合初始化为其自身
2.查找。查找两个元素所在集合,即找祖宗。
3.合并。如果两个元素的集合号不同,将两个元素合并为一个集合。
注意:查找时,递归找祖宗,祖宗集合号等与本身时停止。回归时,把查找路径上的所有结点统一为祖宗的集合号;合并时,只要把一个元素的祖宗集合号改为另一个元素的祖宗集合号。“擒贼先擒王”,只改祖宗即可!
Step 1:初始化
void init(){
for(int i=0;i<=n;i++){
fa[i]=i;
}
}
Step 2:查找
int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
Step 3:合并
void UnionSet(int x,int y){
int nx=find(x);
int ny=find(y);
if(nx!=ny) fa[x]=y;
}
五.并查集板子题及经典例题
一.板子题:(普及-)
洛谷 P3367https://www.luogu.com.cn/problem/P3367
题目描述
如题,现在有一个并查集,你需要完成合并和查询操作。
输入格式
第一行包含两个整数 N,M,表示共有N个元素和M个操作。
接下来M行,每行包含三个整数 Zi,Xi,Yi 。
当Zi=1 时,将Xi 与Yi 所在的集合合并。
当 Zi=2 时,输出Xi 与Yi 是否在同一集合内,是的输出 Y
;否则输出 N
。
输出格式
对于每一个Zi=2 的操作,都有一行输出,每行包含一个大写字母,为 Y
或者 N
。
输入输出样例
输入
4 7 2 1 2 1 1 2 2 1 2 1 3 4 2 1 4 1 2 3 2 1 4
输出
N Y N Y
说明/提示
对于 30% 的数据,N≤10,M≤20。
对于 70% 的数据,N≤100,M≤103。
对于 100% 的数据,1≤N≤104,1≤M≤2×105,1≤Xi,Yi≤N,Zi∈{1,2}。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4+3;//由题目最下方的数据可知,数组要开到1e4(得多加一点)
int f[maxn];
int n,m;
void init(){
for(int i=0;i<=maxn;i++){
f[i]=i;
}
}//初始化
int find(int x){
if(x!=f[x]){
return f[x]=find(f[x]);
}
return f[x];
}//查询操作
void join(int x,int y){
int fx=find(x);
int fy=find(y);
if(fx!=fy){
f[fy]=fx;
}
}//合并操作
int main()
{
init();
cin>>n>>m;//输入
while(m--){
int t,x,y;
cin>>t>>x>>y;//输入
switch(t){
case 1:
join(x,y);//1时合并
break;
case 2:
if(find(x)==find(y)) cout<<"Y"<<endl;
else cout<<"N"<<endl;
break;//按照题目写语法句子
}
}
return 0;
}
二.模板题:
HDU 1232畅通工程https://vjudge.net/problem/HDU-1232
某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?
Input
测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000 )和道路数目M;随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。为简单起见,城镇从1到N编号。
注意:两个城市之间可以有多条道路相通,也就是说
3 3
1 2
1 2
2 1
这种输入也是合法的
当N为0时,输入结束,该用例不被处理。
Output
对每个测试用例,在1行里输出最少还需要建设的道路数目。
Sample Input
4 2 1 3 4 3 3 3 1 2 1 3 2 3 5 2 1 2 3 5 999 0 0
Sample Output
1
0
2
998
//Huge input, scanf is recommended.
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+3;
int f[maxn];
int N,M;
void init(){
for(int i=0;i<=maxn;i++){
f[i]=i;
}
}
int find(int x){
if(x!=f[x]){
return f[x]=find(f[x]);
}
return f[x];
}
void join(int x,int y){
int fx=find(x);
int fy=find(y);
if(fx!=fy){
f[fy]=fx;
}
}
int main(){
scanf("%d%d",&N,&M);
init();
int ans=0;
while(M--){
int x,y;
scanf("%d%d",&x,&y);
if(find(x)!=find(y)){
ans++;//例如1!=2,1 2就能连成一条边,ans++就是边数加一
join(x,y);
}
}
printf("%d\n",N-1-ans);//一共需要N-1条边,已经有了ans条边,还需要N-1-ans条边
return 0;
}
三:模板题(普及-)
洛谷 P1551 亲戚https://www.luogu.com.cn/problem/P1551
题目背景
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
题目描述
规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果 x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。
输入格式
第一行:三个整数 n,m,p(n,m,p≤5000),分别表示有n个人,m个亲戚关系,询问p对亲戚关系。
以下m行:每行两个数 Mi,Mj,1≤Mi, Mj≤N,表示Mi 和Mj 具有亲戚关系。
接下来p行:每行两个数Pi,Pj,询问Pi 和Pj 是否具有亲戚关系。
输出格式
p行,每行一个 Yes
或 No
。表示第i个询问的答案为“具有”或“不具有”亲戚关系。
输入输出样例
输入
6 5 3 1 2 1 5 3 4 5 2 1 3 1 4 2 3 5 6
输出
Yes Yes No
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e3+3;
int f[maxn];
int n,m,p;
void init(){
for(int i=0;i<=maxn;i++){
f[i]=i;
}
}
int find(int x){
if(x!=f[x]){
return f[x]=find(f[x]);
}
return f[x];
}
void join(int x,int y){
int fx=find(x);
int fy=find(y);
if(fx!=fy){
f[fy]=fx;
}
}
int main(){
scanf("%d%d%d",&n,&m,&p);
init();
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
join(x,y);
}
for(int i=1;i<=p;i++){
int q,r;
scanf("%d%d",&q,&r);
if(find(q)==find(r)) printf("Yes\n");
else printf("No\n");
}
return 0;
}