一、概述
1、定义:并查集是一种树型的数据结构
2、应用:用于处理不相交的集合的合并和查询。
二、操作及实现
1、查询:找到自己的老大
cpp
int find(int x)
{
while(f[x]!=x)//如果自己的老大不是自己说明老大另有其人
{
x=f[x];//接着往下找老大
}
return x;//找到了老大
}
2、合并:合并成一个团伙
cpp
void join(int x,int y)
{
int fx=find[x],fy=find[y];//找到两个人各自的老大
if(fx!=fy)//如果两个人的老大不一样说明不是一个团伙
{
f[fx]=fy;//要合并成一个团伙,只能有一个老大,所以说其中一个老大要认另一个老大做新老大。
}
}
3、路径压缩:(优化find函数)
如果树的深度过深,查询起来会相当耗时,所以说要减短树的深度。
可以将查询点x到根节点(也就是老大)的途径的点的父节点都设成为根节点,会大大降低查询的难度。
缺点是:只有当找到老大时,才能进行路径压缩,所以每个团体第一次的查询是没有什么优化的,之后才会生效。
cpp
int find(int x)//路径压缩
{
if(f[x]==x)return x;//如果自己的老大是自己输出
return f[x]=find(f[x]);//如果不是,继续找老大,并且把自己的父节点设为老大
}
三、模板和例题
1、模板
洛谷P3367
https://www.luogu.com.cn/problem/P3367#submit
https://www.luogu.com.cn/problem/P3367#submit

cpp
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int f[N],a,b,c;
int find(int k)
{
//路径压缩
if(f[k]==k)return k;
return f[k]=find(f[k]);
}
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++)
{
cin>>a>>b>>c;
if(a==1)
{
f[find(b)]=find(c);//合并 c所在组赢了b所在组
}
else
{
if(find(b)==find(c))//检查老大是否相同
{
cout<<"Y"<<endl;
}
else
{
cout<<"N"<<endl;
}
}
}
return 0;
}
2、acwing 837、连通块中点的数量
https://www.acwing.com/problem/content/839/

cpp
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,f[N],cnt[N];
int find(int x)//不能用路径压缩因为那样的话就没办法统计连通快的数量了
{
if (f[x] != x) f[x] = find(f[x]);
return f[x];
}
void join(int x,int y)
{
int fx=find(x),fy=find(y);
if(fx!=fy)
{
f[fx]=fy;
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
f[i]=i;
cnt[i]=1;
}
while(m--)
{
string flag;
cin>>flag;
int a,b;
if(flag=="C")
{
cin>>a>>b;
if(find(a)==find(b))continue;
cnt[find(b)]+=cnt[find(a)];//先加连通块数量,否则先操作集合的话会导致重叠。而且要给新老大加,不能加错
join(a,b);
}
else if(flag=="Q1")
{
cin>>a>>b;
if(find(a)==find(b))
{
cout<<"Yes"<<endl;
}
else
{
cout<<"No"<<endl;
}
}
else
{
cin>>a;
cout<<cnt[find(a)]<<endl;
}
}
return 0;
}
3、acwing 240食物链


cpp
//把食物环拆成食物链,用某节点到根节点的距离%3的余数来表示各节点之间的关系。
/*
余1:吃根节点
余2:被根节点吃
余0:同类
*/
#include<bits/stdc++.h>
using namespace std;
const int N=5e4+10;
int f[N],d[N];
int n,m;
int find(int x)
{
if(f[x]!=x)//自己不是根节点
{
int t=find(f[x]);//先压缩路径使除x外的所有上级都指向根节点并记录根节点是谁
d[x]+=d[f[x]];//到根节点的距离更新为x到其父节点的距离+父节点到根节点的距离
f[x]=t;//再让x指向根节点
}
return f[x];
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
f[i]=i;
}
int res=0;
while(m--)
{
int t,x,y;
cin>>t>>x>>y;
if(x>n||y>n)
{
res++;
continue;
}
int fx=find(x),fy=find(y);
if(t==1)//同类
{
//在同一集合中
if(fx==fy&&(d[x]-d[y])%3)res++;//两者%3不相等,说明不是同一类,谎话++
else if(fx!=fy)//不在同一集合中,先到者为真
{
f[fx]=fy;//合并集合,让其中一个老大认另一个人做老大
d[fx]=d[y]-d[x];//因为同类,所以(d[x]+d[fx]-d[y])%3==0
}
}
//同上
else
{
if(fx==fy&&(d[x]-d[y]-1)%3)res++;
else if(fx!=fy)
{
f[fx]=fy;
d[fx]=d[y]+1-d[x];
}
}
}
cout<<res<<endl;
return 0;
}