并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。常常在使用中以森林来表示。
并查集:
1.将两个集合合并
2.询问两个元素是否在一个集合中
基本原理:
每个集合用一棵树来表示。树根的编号就是整个集合的编号。
每个节点存储它的父节点,p[x]表示x的父节点。
主要用途有以下两点:
1、维护无向图的连通性(判断两个点是否在同一连通块内,或增加一条边后是否会产生环)
2、用在求解最小生成树的Kruskal算法里。
并查集的讲解推荐看这个:
https://blog.csdn.net/the_ZED/article/details/105126583?
例题:
合并集合
https://www.acwing.com/problem/content/838/
#include<iostream>
using namespace std;
const int N=100010;
int n,m;
int p[N];
int find(int x)
{
//并查集 按秩合并 用的并不是很多这里不再阐述
if(p[x]!=x) p[x]=find(p[x]); //路径压缩
return p[x];
//return p[x]==x? p[x]:p[x]=find(p[x]);
//find(p[x])相当于进行深搜,找到他的父节点
}
void join(int x,int y)
{
int fx=find(x),fy=find(y);
if(fx!=fy)
p[fx]=fy;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) p[i]=i;
while(m--)
{
char op[2];
int a,b;
cin>>op>>a>>b;
if(op[0]=='M')
{
join(a,b);
//p[find(a)]=find(b);
}else {
if(find(a)==find(b)) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}
return 0;
}
例题:
连通块中点的数量
https://www.acwing.com/problem/content/839/
#include<iostream>
using namespace std;
const int N=100010;
int n,m;
int pre[N],Size[N];
int find(int x)
{
return pre[x]==x?pre[x]:pre[x]=find(pre[x]);
}
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
pre[i]=i;
Size[i]=1;
}
while(m--)
{
char op[3];
int a,b;
cin>>op;
if(op[0]=='C')
{
cin>>a>>b;
if(find(a)==find(b)) continue;//在这里if(a==b)与if(find(a)==find(b)) 是不一样的
//我们在第33行规定了b的父节点为最终的结点,所以我们在第32行,点的数量都落在了find(b)的上面
Size[find(b)]+=Size[find(a)];//父节点所代表的连通块中点的数量相加
pre[find(a)]=find(b);//将两个连通块合并为一个连通块
}else if(op[1]=='1')
{
cin>>a>>b;
if(find(a)==find(b)) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}else
{
cin>>a;
cout<<Size[find(a)]<<endl;
}
}
return 0;
}
例题:食物链
https://www.acwing.com/problem/content/242/
#include<iostream>
using namespace std;
const int N=100010;
int pre[N];
int Root[N];
int find(int x)
{
if(pre[x]!=x)
{
int u=find(pre[x]);
Root[x]+=Root[pre[x]];//最终得到的是单个到根节点的距离,我们用距离来表示种类
pre[x]=u;
}
return pre[x];
}
int main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++) pre[i]=i;
int num=0;
//当前的话与前面的话产生冲突
//x吃x
while(k--)
{
int d,x,y;
cin>>d>>x>>y;
if(x>n||y>n) num++;
else
{
int px=find(x),py=find(y);
//用px与py的关系判断是否在同一个集合中
if(d==1)//x y是同类
{
if(px==py&&(Root[y]-Root[x])%3)// 如果x,y在同一个集合,如果到根节点的距离不同则为不同的类
{
num++;
}else if(px!=py) //如果x,y不在同一个集合,所以无法判断真假,故使它为真
{
pre[px]=py;//我们是以py为所有的父节点,因此Root[px]+Root[x]==Root[y];
//x———px-->py————y
Root[px]=Root[y]-Root[x];
}
}else//x吃y
{
if(x==y) num++;
else if(px==py&&(Root[x]-Root[y]-1)%3)//因为x是吃y的,所以x比y多1
{
num++;
}else if(px!=py)
{
pre[px]=py;//进行这一步的原因主要是保证新询问的要再加入到同一个集合中
//x———px-->py————y
Root[px]=Root[y]+1-Root[x];
}
}
}
}
cout<<num;
return 0;
}