并查集
并查集我个人认为一种用来处理某些特殊数据结构的算法
这种算法有两个操作,合并与查询
- 合并:能够高时效的将某一些符合题目要求的数据合并在一个集合中
- 查询:能够高时效的查询某个指定数据是否存在于某个集合之中,或者是计算满足题目要求的集合数量
那么这个算法是如何运作的呢?
接下来我们看几道例题
例题1.亲戚
这是一道十分简单的并查集入门题,这里用来讲述并查集的运作思路。
根据题意,我们可以得到如果B是A的亲戚,C是B的亲戚,那么ABC三个人互为亲戚,也就是说ABC三个节点可以被放置在同一个集合当中,为了方便表述,我们可以认为这是一棵树,A是根,B与C都是A的子节点。
由此,我们便可以利用并查集,将有亲戚关系的两个节点,进行挂靠,在查询时,只要利用一个递归,不停向上询问,直到问到自己的根节点,如根节点相同,便是有亲戚关系。
但是,还有一个问题,如此下来某个点都向上询问一遍,将会耗费大量的时间,那么何来高时效之说呢?
这里我们需要对并查集做一个优化,使其能更快的计算出自己的根节点,我称之为缩点,将所有有关系的点都进行直接挂靠,也就是说,构造出一颗深度为2的树,如图。
如此一来,在查询的时候就可以直接访问到自己的根节点了。
#include<stdio.h>
#include<algorithm>
int n,m,a,b,fa[10005];
int find(int x){ //查询函数
if(fa[x]!=x) fa[x]=find(fa[x]); //缩点:反复向上询问,询问到根节点才赋值
return fa[x];
}
int main(){
scanf("%d%d",&n,&m);
for(register int i=1;i<=n;i++){fa[i]=i;} //初始化,开始时每个节点都是一个根节点
for(register int i=1;i<=m;i++){
scanf("%d%d",&a,&b);
a=find(a);b=find(b);
if(a!=b) fa[b]=fa[a]; //合并
}
int Q;
scanf("%d",&Q);
for(register int i=1;i<=Q;i++){
scanf("%d%d",&a,&b);
if(find(a)==find(b)) printf("Yes\n"); //根节点相同
else printf("No\n");
}
return 0;
}
通过这道题目,我们可以了解了并查集的基本运行思路,其实很多题的基本代码也是依照这个作为模板,不过在此基础上作出一些变化。
例题2.黑社会团伙
由题意可知,这题与上一题大题框架一样,变化的地方在于
- 求集合数量
- 多出了新的概念,我敌人的敌人也是我的朋友
由新的概念便可以引申出许多新的关系,稍作整理,就有如下关系
- 我朋友的朋友是我的朋友
- 我朋友的敌人是我的敌人
- 我敌人的敌人是我的朋友
- 我敌人的朋友是我的敌人
未完待续......