数据结构 并查集 并查集的操作以及结构
并查集就支持两个操作
- 将两个集合合并
- 询问两个元素是否在一个集合中
并查集可以在接近o1的时间内,完成这两个操作
别忘了,集合中的元素是不重复的,一般情况下,考察并查集的所有元素也不重复
并查集的结构
并查集是一种树形结构
每个节点存储自己的父节点,例如
f[1]=1,f[2]=1,f[3]=1,f[4]=3,f[5]=3,f[6]=4
并查集初始化
在最初,每个元素都是自己独立一个集合,所以并查集初始化如下
for(int i=1;i<=n;i++)f[i]=i;
并查集查找操作
int find(int x){
if(f[x]==x)return x;//找到根节点了,因为他指向自己
return find(f[x]);//不是根节点,继续向上找
}
举例,红色是找过去,绿色是把返回值返回回来
并查集查找优化
并查集既然是只需要支持查找集合,合并集合的操作
那我们可以尽量让这个数变宽,变浅,让子节点在尽量短的路径里,找到根节点
那我们在查找时,就可以更改其他子节点,让所有子节点,直接指向根节点
这样下次再查找时,现有子节点,即可一步(o1)找到根节点
int find(int x){
if(f[x]==x)return x;
return f[x]=find(f[x]);//只增加一步操作,将此次查找经过的子节点,其父节点直接指向根节点
}
如下图
并查集合并集合
要记住,f[i]里的i,才是集合里的元素的值,而f[i]的值,是其父节点的值
合并集合,就是把集合A的根节点i,对应的f[i]的值
变成集合B的根节点i的值
代码,将x集合合并到y(x指向y)
void unionset(int x,int y){
//先用查找函数,找到根节点,然后把根节点f[i]指向的值,从他本身,变为find(y)的集合的根节点的值
f[find(x)]=find(y);
}
典型例题以及完整代码实现
AcWing - 算法基础课
#include<iostream>
using namespace std;
const int N = 100100;
int f[N];
//查找
int find(int x){
if(f[x]==x)return x;
return f[x]=find(f[x]);
}
//合并
void on(int x,int y){
f[find(x)]=find(y);
return;
}
int main(){
int n,m;
cin>>n>>m;
//初始化
for(int i=1;i<=n;i++)f[i]=i;
while(m--){
char c;int a,b;
cin>>c>>a>>b;
if(c=='M'){
on(a,b);
}
else
{
cout<<(find(a)==find(b)?"Yes":"No")<<endl;
}
}
return 0;
}
连通块中点的数量,经典例题
AcWing - 算法基础课
此题和并查集样题区别是
多了一个查询操作:查询任意一节点所在连通块的数量
连通块查询思路
我们要做的就是,新建一个fs数组,假如根节点是i,则fs[i]存储的就是根节点所在连通块的节点数量
每个连通块的根节点对应一个fs[i]数组,fs[i]的值是该连通块的节点数量
连通块计算思路
一开始,所有fs[i]=1;因为所有节点都是独立的
在每次合并时,例如x所在连通块合并到y所在连通块
我们将x的根节点指向的该连通块所有节点的数量,加在y的根节点指向所有节点数量上
这样便增加fs[i]根节点的值
void on(int x,int y){
//合并两个连通块(集合)时,先把连通块对应的值计算,因为合并连通块后,对应的fs的值也变了
//因为该节点的根节点i,f[i]才是该连通块所有节点的数量
//一定是y累加x的值,因为下面最后一行,是将y的连通块的根节点作为合并后的根节点
fs[find(y)]+=fs[find(x)];
f[find(x)]=find(y);
}
完成代码实现
#include<iostream>
using namespace std;
const int N = 100010;
#define ll long long
int f[N],fs[N];//fs[i]的值代表i节点作为根节点,共有多少连通块
int find(int x){
if(f[x]==x)return x;
return f[x]=find(f[x]);
}
void on(int x,int y){
//合并两个连通块(集合)时,先把连通块对应的值计算,因为合并连通块后,对应的fs的值也变了
//因为该节点的根节点i,f[i]才是该连通块所有节点的数量
//一定是y累加x的值,因为下面最后一行,是将y的连通块的根节点作为合并后的根节点
fs[find(y)]+=fs[find(x)];
f[find(x)]=find(y);
}
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
f[i]=i;
fs[i]=1;//所有节点,一开始节点所在的连通块只有自己本身
}
while(m--){
string c;int a,b;
cin>>c;
if(c=="C"){
cin>>a>>b;
//同一个节点不用合并,避免多合并,造成根节点对应的数组的值增大
if (find(a) == find(b)) continue;
on(a,b);
}
else if(c=="Q1"){
cin>>a>>b;
cout<<(find(a)==find(b)?"Yes":"No")<<endl;
}
else
{
cin>>a;
//输出时,也是该连通块根节点的fs[i]代表连通块数量
//别忘了先查询,再输出
cout<<fs[find(a)]<<endl;
}
}
return 0;
}