定义
并查集是一种维护集合的数据结构,并-Union,查-Find ,集-Set。即支持合并和查找两种操作。
注:同一个集合只存在一个根结点
int father[N];//数组实现
father[i]=j;//表示元素i的父亲结点是j
father[i]=i;//表示元素i的父亲结点是i,即i是集合的根结点
//则i与j组成了一个以根结点为i的集合
基本操作
1.初始化
//初始化
for(int i=0;i<n;i++)
father[i]=i;
2.查找根结点
//递推实现-通过循环实现
int findFather1(int n){
//当father[n]=n时,n才为根结点
while(n!=father[n])
n=father[n];
return n;
}
//递归实现-通过不断调用自身实现
int findFather2(int n){
if(n==father[n]) return n;
else {
n=father[n];
return findFather2(n);//即return findFather2(father[n]);
}
}
3.合并
//将两个数的不同集合合并成一个集合
void merge(int a,int b){
int fa=findFather1(a);
int fb=findFather1(b);
if(fa!=fb)
father[fb]=fa;//为这两个集合建立联系,使之变成同一集合
}
4.路径压缩
由于上面所建立的并查集特别长,每次找根结点时均要从最底下回溯,效率非常低,于是考虑将所有结点均变成根结点的直接分支,此时高度就变成了1。
//递推实现
int findFather1(int n){
int a=n;
while(n!=father[n])
n=father[n];//找根结点,n为根结点
while(a!=father[a]){
int x=a;
a=father[a];//不断回溯
father[x]=n;//改新父亲为根结点
}
return n;
}
//递归实现
int findFather2(int n){
//找根结点,n为根结点
if(n==father[n]) return n;
else{
int N=findFather2(father[n]);//回溯
father[n]=N;//改新父亲为根结点
return N;
}
}
应用
思路:将不同组好朋友看成不同集合,分组就是集合合并的过程。
1.合并:边输入同时边合并即可实现。
2.计数:对于计算组数,我们可以设置一个hash数列,以根结点为下标-key,值为布尔型,最后累加即可得到结果。
#include <iostream>
using namespace std;
const int maxn=100;
int father[maxn];
bool isfather[maxn]={false};//标记是否为根结点
void initial(int n){
for(int i=0;i<n;i++)
father[i]=i;
}
int findFather(int x){
int a=x;
//找根结点
while(x!=father[x])
x=father[x];
//压缩路径
while(a!=father[a]){
int b=a;
a=father[a];
father[b]=x;
}
return x;
}
void Union(int a,int b){
int fa=findFather(a);
int fb=findFather(b);
if(fa!=fb)
father[fb]=fa;
}
int main(){
int num,group,sum=0;
int a,b;//装元素
cin>>num>>group;
initial(num);//初始化
for(int i=0;i<group;i++){
cin>>a>>b;
Union(a,b);//合并
}
for(int i=1;i<=num;i++){
isfather[findFather(i)]=true;//改根结点为true;
}
//计算根结点数,即合并后组数
for(int i=1;i<=num;i++){
sum+=isfather[i];//bool型转换为整型:false->0;true->1;
}
cout<<sum<<endl;
return 0;
}