并查集是若干个集合,可以判断两个对象是否在一个集合中,从而判断它们需不需要进行一个操作。
每个集合都是一棵树(注意不一定是二叉树),这里说一下二叉树和其他树的一点区别,二叉树编号是按位置来的(一棵二叉树一开始是怎样就一直是怎样,变的只是每个节点的值,编号永远不会改,父亲节点也永远不会改),而其他树的编号是按新建节点的顺序来的,越前添加的新节点编号越小(这个编号可以移动,比如某个节点的父亲节点可以变成它的父亲节点的父亲节点,这也就是并查集的一种操作)。
比如看一道例题
有若干个强盗 有若干个线索,每个线索中的两个强盗为同伙(倘若A与B是同伙,B与C是同伙,那么A与C也是同伙),判断有多少个独立的犯罪团伙。
样例数据:
10 9//10个强盗 9条线索
1 2
3 4
5 2
4 6
2 6
8 7
9 7
1 6
2 4
显然,在线索输入之前,每个强盗都是一个犯罪团伙,每次读入一个线索,就将为同伙的两个强盗的团伙合并为一个团伙。
为了方便,每个团伙都会有一个“老大”,至于有什么用,待会就知道了,显然一开始每个强盗都是自己团队的老大,合并的时候需要找到两个团伙的老大,然后将其中一个老大的上司更新为另一个老大。
fa[i]代表第i个强盗的直接上司的编号,注意是直接上司,它的直接上司有可能还有上司,直到它的上司的上司的上司……(若干个上司)的上司仍是自己的时候,这个强盗才是这个团伙真正的老大,显然,最后只需要判断,有多少个强盗的老大就是他自己,就是最终答案。
上代码
#include<cstdio>
using namespace std;
int fa[500];
void together(int x,int y){
fa[x]=y;//y变成了x团伙的老大的老大
return ;
}
//有些代码还查询x与y的树的深度 再判断是x合并到y还是y合并到x更优化 而这里有将节点直接接到根节点的优化 所以循环不会太多 理论上应该不会超过3次
int dfs(int h){
if(fa[h]==h)return h;
return fa[h]=dfs(fa[h]);
}
/*
这里函数很简单,但是有个很重要的优化。
如果一个强盗的上司是他自己说明他就是老大 那么直接返回他自己(这个很简单)
而当一个强盗的上司不是他自己时,就将他的上司赋值为他的上司的老大,比如说一个强盗的上司的上司的上司......(200个上司后)的上司是老大 那么如果再次查询这个点又要循环200次 而如果直接赋值成老大 那么他只需要向上搜一次 即使将这个团伙合并到别的团伙(即他的老大的上司被赋值了)也只需要搜两次
*/
int main(){
int i,j,k,m,n,x,y,ans=0;
scanf("%d %d",&n,&m);
for(i=1;i<=n;i++)fa[i]=i;//当读入线索之前,每个团伙的强盗都只有一个,每个强盗都是自己团伙的老大
for(i=1;i<=m;i++){
scanf("%d%d",&x,&y);
together(dfs(x),dfs(y));//dfs[h]代表第h个强盗的老大
}
for(i=1;i<=n;i++){
if(fa[i]==i)ans++;
}
printf("%d",ans);
return 0;
}
本文详细介绍了并查集算法的基本概念及其应用实例。通过解决独立犯罪团伙数量的问题,阐述了如何利用并查集来判断对象间的关系,并给出了具体的实现代码。
313

被折叠的 条评论
为什么被折叠?



