https://blog.csdn.net/qq_38735931/article/details/81543380
并查集
并查集的基本操作
1.初始化
2.查找
//1.初始化father数组--------分散的点
//一开始,每个元素都是独立的一个集合,因此需要另所有father[i]=i
for(int i=1;i<=N;i++){
father[i]=i;
}
//2.根据需要进行查找或合并
//由于同一个集合中只存在一个根节点,因此查找操作就是对给定的节点寻找其根节点的过程
//实现的方式可以是递推或递归//反复寻找父亲节点,直到找到根节点(即father[i]=i的节点)
//findFather函数返回元素x所在集合的
//寻找根节点
int findFather(int x){
while(x!=father[x]){//如果不是根节点,继续循环//father[father[x]]
x=father[x];//获得父亲节点//往上一层(父亲节点)去翻去找//父亲的父亲,你代表父亲
}
return x;
}
这个查找的过程也可以用递归来实现
int findFather(int x){
if(x==father[x])return x;//如果找到根节点,则返回根节点编号
else return findFather(father[x]);//否则,递归判断x的父亲节点是否是根节点
}
3.合并
//合并是指将2个集合合并成1个集合
//题目中一般给出2个元素,要求把这2个元素所在的集合合并
//合并的过程:把其中1个集合的根节点的父亲指向另1个集合的根节点
//1.判断给定的a、b 这2个元素是否属于同1个集合(只有当2个集合属于不同集合才合并)
//可以调用查找函数,对这2个元素a、b分别查找根节点,然后判断其根节点是否相同
//2.合并2个集合,在1中已经获得了2个元素根节点faA和faB,只需要把其中1个父亲节点指向另1个节点
//father[faA]=faB或者father[faB]=faA
//例子,把元素4和元素6合并
//1.判断4和6是否属于同1个集合
//元素4所在的集合的根节点是1
//元素6所在的集合的根节点是5
//因此他们不属于同一个集合
//2.合并2个集合
//father[5]=1
//于是有了合并后的集合
int findFather(int x){
if(x==father[x])return x;//如果找到根节点,则返回根节点编号
else return findFather(father[x]);//否则,递归判断x的父亲节点是否是根节点
}
void Union(int a,int b){
int faA=findFather(a);//查找a的根节点,记成faA
int faB=findFather(b);//查找b的根节点,记成faB
if(faA!=faB){//如果不属于同一个集合
father[faA]=faB;//合并它们
}
}
并查集产生的每1个集合都是1棵树
路径压缩
int findFather(int x){
if(x==father[x])return x;
else findFather(father[x]);
}
如果给出的元素很多并且形成一条链,
那么这个查找的效率很低
//总共10^5个元素形成1条链,每次查询要查询到最后面的节点的根节点
//那么每次都要花费10^5的计算量去查找,这显然无法承受
==>优化查询操作
由于father函数的目的就是查找根节点
//例如下面这个例子
father[1]=1;
father[2]=1;
father[3]=2;
father[4]=3;
//因此,如果只是为了查找根节点,那么可以把操作等价为
father[1]=1;
father[2]=1;
father[3]=1;
father[4]=1;
对一个的图形的变化过程如图
这样就相当于把当前查询节点的路径上的所有节点的父亲都指向根节点,
查找的时候不需要一直回溯去找父亲了,
查询的复杂度降为O(1)
那么,如何,实现这种转换呢?
回忆以前查找函数findFather()的查找过程,
可以回到是从给定节点不断获得其父亲节点最终到达根节点的
因此转换的过程可以概括为如下2个步骤
1.按原先的写法获得x的根节点r
2.重新从x开始走一遍寻找根节点的过程,把路径上经过的所有节点的父亲全部改为根节点r
于是可写出代码
int findFather(int x){
int a=x;//由于x在下面的while中会变成根节点,因此先把原来的x保存一下
while(x!=father[x])x=father[x];//寻找根节点
//到这里,x存放的是根节点,下面把路径上的所有节点的father都改为根节点
while(a!=father[a]){
int z=a;//因为a要被father[a]覆盖,所以先保存a的值,来修改father[a]
a=father[a];//回溯父亲节点
father[z]=x;//将原先的节点a的父亲改为根节点x
}
return x;
}
递归写法
int findFather(int v){
if(v==father[v])return v;
else{
int F=findFather(father[v]);//递归寻找father[v]的根节点
father[v]=F;//将根节点F赋予给father[v]
return F;//返回根节点F
}
}
例子
//7 5
//1 2
//2 3
//3 1
//1 4
//5 6
//3
#include <iostream>
#include <cstdio>
using namespace std;
const int N=100;
int father[N];//存放父亲节点
bool isRoot[N];//记录每个节点是否作为某个集合的根节点
int findFather(int x){
int a=x;
while(x!=father[x])x=father[x];//----->得到根节点x---------father[i]=i
while(a!=father[a]){//----->//回到原来的点---让路径上的所有的节点的父节点是根节点x
int t=a;
a=father[a];
father[t]=x;
}
return x;
}
void Union(int a,int b){//合并a,b所在的集合
int faA=findFather(a);//找a的根节点
int faB=findFather(b);//找b的根节点
if(faA!=faB)
father[faB]=faA;
}
void init(int n){//初始化father[i]为i,且flag[i]为false
for(int i=1;i<=n;i++){
father[i]=i;
isRoot[i]=false;
}
}
int main()
{
//好朋友
int n,m,a,b;//宝贝个数//好朋友组数
scanf("%d%d",&n,&m);//75
init(n);//要记得初始化
for(int i=0;i<m;i++){
scanf("%d%d",&a,&b);//输入两个好朋友的关系
Union(a,b);//合并a,b所在的集合
}
for(int i=1;i<=n;i++){
isRoot[findFather(i)]=true;//i的根节点是findFather(i)
}
int ans=0;//记录集合数目
for(int i=1;i<=n;i++){
ans+=isRoot[i];
}
printf("%d\n",ans);
return 0;
}
// #include <iostream>
// #include <cstdio>
// using namespace std;
// const int N=100;
// int father[N];//存放父亲节点
// int isRoot[N];//记录每个节点是否作为某个集合的根节点
// int findFather(int x){
// int a=x;
// while(x!=father[x])x=father[x];//----->得到根节点x---------father[i]=i
// while(a!=father[a]){//----->//回到原来的点---让路径上的所有的节点的父节点是根节点x
// int t=a;
// a=father[a];
// father[t]=x;
// }
// return x;
// }
// void Union(int a,int b){//合并a,b所在的集合
// int faA=findFather(a);//找a的根节点
// int faB=findFather(b);//找b的根节点
// if(faA!=faB)
// father[faB]=faA;
// }
// void init(int n){//初始化father[i]为i,且flag[i]为false
// for(int i=1;i<=n;i++){
// father[i]=i;
// isRoot[i]=0;
// }
// }
// int main()
// {
// //好朋友
// int n,m,a,b;//宝贝个数//好朋友组数
// scanf("%d%d",&n,&m);//75
// init(n);//要记得初始化
// for(int i=0;i<m;i++){
// scanf("%d%d",&a,&b);//输入两个好朋友的关系
// Union(a,b);//合并a,b所在的集合
// }
// for(int i=1;i<=n;i++){
// isRoot[findFather(i)]++;//i的根节点是findFather(i)
// }
// //int ans=0;//记录集合数目
// for(int i=1;i<=n;i++){
// //ans+=isRoot[i];
// printf("%d:%d\n",i,isRoot[i]);
// }
// return 0;
// }
//其每一集合中元素的数目------包括根节点(自己)
// 7 5
// 1 2
// 2 3
// 3 1
// 1 4
// 5 6
// 1:4
// 2:0
// 3:0
// 4:0
// 5:2
// 6:0
// 7:1
//|并查集|7-10 排座位 (25分)
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int relation[110][110];
int father[110];
void init(int n){
for(int i=1;i<=n;i++)
father[i]=i;
}
int findFather(int x){
// //非递归版路径压缩
// int a=x;
// //1.寻找根节点
// while(x!=father[x])x=father[x];
// //2.父节点都改为根节点
// while(a!=father[a]){
// int t=a;
// a=father[a];
// father[t]=x;
// }
// return x;
// //非递归版路径压缩
if(x==father[x])return x;
else{
int F=findFather(father[x]);
father[x]=F;
return F;
}
}
void Union(int a,int b){
int a_root=findFather(a);
int b_root=findFather(b);
if(a_root!=b_root)
father[b_root]=a_root;
}
int main()
{
// memset(relation,0,sizeof(relation));
int n,m,k;
cin>>n>>m>>k;
init(n);
while(m--){
int a,b,r;
cin>>a>>b>>r;
if(r==1)
Union(a,b);
else{
relation[a][b]=-1;
relation[b][a]=-1;/
}
}
while(k--){
int x,y;
cin>>x>>y;
int x_root=findFather(x);
int y_root=findFather(y);
if(relation[x][y]==-1){
if(x_root==y_root)//敌对但是有共同好友(相同根节点)
cout<<"OK but..."<<endl;
else//敌对没有共同好友(相同根节点)
cout<<"No way"<<endl;
}else{
if(x_root=y_root)//互相只是好友
cout<<"No problem"<<endl;
else//互相没有关系
cout<<"OK"<<endl;
}
}
return 0;
}
/**
7 8 4
5 6 1
2 7 -1
1 3 1
3 4 1
6 7 -1
1 2 1
1 4 1
2 3 -1
3 4
No problem
5 7
No problem
2 3
OK but...
7 2
No way
**/