一、问题描述
目前有一些强盗,警察在破案的过程中想知道一共有几个犯罪团伙。
可以根据线索进行份分析:
现在有10个强盗,给他们编号为1到10.
1和2是同伙;
3和4是同伙;
5和2是同伙;
4和6是同伙;
2和6是同伙;
8和7是同伙;
9和7是同伙;
1和6是同伙;
2和4是同伙。
有一点需要注意,强盗同伙的同伙也是同伙。
二、问题分析
首先我们假设10个强盗是互相不认识的,一个就是一个团伙,接下来根据线索进行归队过程。
第一步:申请一个一维数组,用数组下标1-10表示10个强盗,用下边对应的表格表示他们是属于哪个团伙的。
第二步:初始化,10个强盗每个都是一个团伙。
第三步:合并同伙,如果发现两个强盗是同伙,他们就是属于一个犯罪团伙。
根据“靠左法则”和“擒贼先擒王法则“””那么就得到最后的结果(这个过程如果不理解的话可以自己在纸上画一遍):
团伙1:1,2,3,4,5,6
团伙2:7,8,9
团伙3:10
因此有三个独立的犯罪团伙。
三、代码复现
//并查集
#include<stdio.h>
int f[1000]={0},n,m,k,sum=0;
//这里是重要的初始化,数组里边存的是自己数组下标的编号就好了
void init()
{
int i;
for(i=1;i<=n;i++)
f[i]=i;
}
//这里是找爹的递归函数, 不停的去找爹,直到找到祖宗为止,其实就是去找犯罪团伙的最高领导人,“擒贼先擒王”原则
int getf(int v)
{
if(f[v]==v)
return v;
else
{
//这里是路径压缩,每次在函数返回的时候,顺带把路上的人的boss改为找到的祖宗的编号,也就是犯罪团伙人最高领导人的编号
//这样可以提高今后找犯罪团伙人的最高领导人,其实就是树的祖先的速度
f[v]=getf(f[v]);
return f[v];
}
}
//这里是合并两个子集合的函数
void merge(int v,int u)
{
int t1,t2;
t1=getf(v);
t2=getf(u);
if(t1!=t2)//判断两个结点是否在同一个集合中,是否是同一个祖先
{
f[t2]=t1;
//靠左原则,左边变成右边的boss,即把右边的集合,作为左边集合的子集合
//经过路径压缩后,将f[u]的根的值也赋值为v的祖先f[t1].
}
}
//主函数
int main()
{
int i,x,y;
scanf("%d %d",&n,&m);
//必须的初始化
init();
for(i=1;i<=m;i++)
{
//开始合并犯罪团伙
scanf("%d %d",&x,&y) ;
merge(x,y);
}
//最后扫描有多少个独立的犯罪团伙
for(i=1;i<=n;i++)
{
if(f[i]==i)
sum++;
}
getchar();
getchar();
printf("%d\n",sum);
return 0;
}
四、总结
并查集通过一个一维数组来实现,其本质就是维护一个森林。
刚开始的时候 ,森林中的每个点都是孤立的,也可以理解为一个点就是一棵树。
之后通过一些条件,主键合并为一棵大树。
其实合并的过程就是“认爹”的过程,在这个过程中,要遵守“靠左法则”和“擒贼先擒王”的法则。
在每次判断两个结点寿佛已经子同一棵树中的时候(一棵树其实就是一个集合),也要注意必须求其根源,中间父亲结点是不能说明问题的,必须找到其祖宗,判断两个结点的祖宗是否在同一个根节点才行。