并查集顾名思义合并,查找集合。是对不相交集合的一种灵活处理方式。
并查集的处理方式
1:初始化,把每个点所在集合初始化为其自身;
2:查找元素所在集合,即查找元素根节点;
3:合并,即把两个元素所在集合合并为一个集合。合并两个不相交集合并判断两个元素是否属于一个集合;
就杭电1232畅通工程这题分析一下
题目链接http://acm.hdu.edu.cn/showproblem.php?pid=1232
首先在地图上给你若干个城市,这些城市可以看做点,然后告诉你哪些城市质检室有道路直接连接的,最后要解决的是整幅地图的联通问题。
举个栗子,随意给你两个点,让你判断他们是否联通,或者问你整幅图有几个连通分支,即问你这幅图被跟成了几个互相独立的块,即抽象一点说成集合。。
回到畅通工程这题,问你还需要修几条路,就是让你判断有几个联通分支。如果有一个连通分支,那么说明地图上的点都已经被连起来了,所以要修的路就是0条;如果有两个连通分支,那就是要再修一条路,从这两个分支中任选一点,把他们连接起来那么所有点都是连起来的了;如果是三个。。。。
那给出第一组数据来说4 2 1 3 4 3;有四个城市,两条路,城市1城市3相连,城市4城市3相连,那么整幅图就被分成了1-3-4与2这两部分,所以有两个连通分支,只要把城市2跟其他任意一个点连起来那么这个整个图就是联通的了,而输出结果是1。好了,那么当有好多城市有好多路的时候我们该怎么做呢?铛铛铛铛~好用的并查集登场了
首先按照一般的并查集处理步骤,先将每个点所在集合初始化为其自身,我用pre【maxn】数组来保存每个点的父节点。然后调用查找函数
int pre[1000 ];
int find(int x) //查找根节点
{
int r=x;
while ( pre[r ] != r ) //返回根节点 r
r=pre[r ];
int i=x , j ;
while( i != r ) //路径压缩
{
j = pre[ i ]; // 在改变上级之前用临时变量 j 记录下他的值
pre[ i ]= r ; //把上级改为根节点
i=j;
}
return r ;
}
找到集合的根节点之后呢,就到了我们并查集的第三部:合并,再调用一次join函数
void join(int x,int y) //判断x y是否连通,
//如果已经连通,就不用管了 //如果不连通,就把它们所在的连通分支合并起,
{
int fx=find(x),fy=find(y);
if(fx!=fy)
pre[fx ]=fy;
}
好了并查集现在就说完了~什么你问我靠!说好的好理解呢?!那我们下面说一个更有爱的栗子。俗话说人在江湖飘谁能不挨刀,行走江湖就要随时做好被砍的准备,为了不被砍死我们就要广交朋友嘛,毕竟多了一个朋友就少了一个回来砍我们的人~当然我们江湖中人讲究的就是一个义字,首先我们不会砍朋友!其次本着朋友的朋友就是我的朋友的原则,所以朋友的朋友我们也不会砍。但是呢,遇到朋友还好说,毕竟都是见过的,要是遇到朋友的朋友,一不小心我们干起来了,然后有人快被砍死了才发现我靠你是那谁谁的人啊?我靠我也是,不好意思这次砍伤了你,我们就此别过,下次不会了,呵呵。。砍错一次人之后呢,你就会学乖了,下次动手前先问一遍,你哪一派的啊?对方说我少林的,你一想哎呀我干爹还在少林寺干过呢,那不行,是自己人,咱们就握手言和不打了。朋友少还好说,万一朋友上万了,打之前还要不断问一遍你上级,你认识那谁嘛?
上级说,不认识啊,我再问问我大哥,大哥又问问他掌门。。。。这样一天下来,大哥大说不认识,打!人早跑了~所以呢就有了我们并查集中的路径压缩~
int find(int x) //查找我(x)的掌门
{
int r=x; //委托 r 去找掌门
while (pre[r ]!=r) //如果r的上级不是r自己(也就是说找到的大侠他不是掌门 = =)
r=pre[r ] ; // r 就接着找他的上级,直到找到掌门为止。
int i=x , j ;
while( i != r ) //路径压缩
{
j = pre[ i ]; // 在改变上级之前用临时变量 j 记录下他的值
pre[ i ]= r ; //把上级改为根节点
i=j;}//把每个分支都变成掌门的直系手下
return r ; //掌门驾到~~~
}
现在大家明白了吧~
附上杭电1232这题代码
#include<stdio.h>
#include<string.h>
int n,m;
int p[1005];
int find(int x)
{
int g = x,h;
while(p[x] != x)
x = p[x];
while(p[g] != x)
{
h = p[g];
p[g] = x;
g = h;
}
return x;
}
int main(void)
{
int i,j,k;
int la,lb;
int c;
while(scanf("%d",&n)!=EOF)
{
if(n == 0)
return 0;
scanf("%d",&m);
c = n;
//开始n个联通分支
for(i = 1; i <= n; i++)
p[i] = i;
for( i = 1; i <= m; i++)
{
scanf("%d%d",&j,&k);
la = find(j);
lb = find(k);
if(la != lb)
{
p[la] = lb;
c--;//每次合并一个就减少一个联通分支
}
}
printf("%d\n",c-1);
}
return 0;
}
并查集博大精深,一定多做些题目学会好好运用他!