HDU/HDOJ 1232 超详细题解(并查集入门教程)

来源:互联网 发布:数据挖掘常用软件 编辑:程序博客网 时间:2024/05/18 00:53

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1232

 

HDU1232这道题属于并查集

 

思路:

 

城市之间由道路连接,相连的城市可以看做一个集合,如:a、b相连,c、d相连,则a和b属于集合A,c和d属于集合B。之后又有人告诉你b和e相连,那么就把e加入到集合A中,以此类推。然后不同集合若是想组成一个大的集合,即集合A和集合B若相连接在一起,那随便在两个集合中分别找两个城市连接在一起就可以了。

 

在这里稍微讲解一下并查集,并查集一般由一个整形数组和两个函数构成,其中数组pre[x]记录的是x的前导节点,若把并查集中的节点看做数的话,那pre[x]数组中记录的就是x的父节点。

 

大家都知道树有个根节点,我们把集合A和集合B分别看成两棵树,每棵树上的节点都可以互相连接,那任意给我两个节点我怎样才能分辨是否相连通呢?我们就可以判断他的根节点是什么,若根节点相同,说明两个节点都属于集合A(或集合B)并且可以连通。

 

所以可以通过find函数来寻找某节点的根节点:

 

int find (int x){int r;//r代表的是根节点while(pre[r]!=r){//根节点的pre存的是它自己r=pre[r];}return r;}

题目中给我们的是x,y两个城市的编号,告诉我们他们可以连接,那我们如何用代码来体现呢?

 

我们可以写一个join函数,用于把x,y加入到某个集合中去,证明他们能连通。

 

可以通过修改x或y根节点的前导数组pre来实现:


int join(int x,int y){int r_x=find(x),r_y=find(y);//寻找到x和y的根节点,判断x,y是否连通if(r_x!=r_y){//若两者的根节点相同就不用执行了//让x的根节点的前导改为y节点的根节点,相当于把x和y所在的树合并成一颗树。这样x,y在树中连通。pre[r_x]=r_y;//这里也可以写成pre[r_y]=r_x,只要把x,y的根节点改成一样就行了}}

优化:

大家想想还有没有什么优化方法?

 

当然是有的,若是经常判断某两个节点的根节点是什么,每次都要执行一次find里面的循环,有没有觉得很浪费?我们明明找一次根节点就行了。

 

看图:


这样的话找f的根节点的时候直接找pre[f]就行了,不用每次遍历一次,一次修改,终身受用。

 

所以find函数可以改成这样:

int find(int x){    int r=x;    while(pre[r]!=r){        r=pre[r];    }    //找到根节点r之后,把这棵树的所有节点的前导全部改为r这样就不用每次都上探寻找一次根节点了    int i=x,j;//j是个临时变量,存放i的前导    while(i!=r){//一直执行到根节点        j=pre[i];        pre[i]=r;        i=j;    }//把经过的节点的前导全改成r    return r;}

这个方法专业点的叫法是——路径压缩。

 

还有个注意的地方,就是开始必须初始化所有的节点的前导都是自己,也就是pre[x]=x;

 

好了,分析过程都讲完了,贴个完整的代码:

 

#include <stdio.h>#include <stdlib.h>#include <string.h>int pre[1010];int find(int x){//查找并返回某个节点的根节点    int r=x;//根节点root    while(pre[r]!=r){        r=pre[r];    }    int i=x,j;    while(i!=r){//路径压缩,让所有节点的pre直接指向根节点        j=pre[i];        pre[i]=r;        i=j;    }    return r;}void join(int x,int y){    int r_x=find(x),r_y=find(y);    if(r_x!=r_y){        pre[r_x]=pre[r_y];    }}int main(){    int hash[1010];    int n,m,i,x,y,sum;while(scanf("%d%d",&n,&m)&&n){ //初始化        sum=0;        memset(hash,0,sizeof(hash));        for(i=1;i<=n;i++){            pre[i]=i;        }        while(m--){            scanf("%d%d",&x,&y);            join(x,y);        }        for(i=1;i<=n;i++){            hash[find(i)]=1;        }        for(i=1;i<=n;i++){            sum+=hash[i];        }        printf("%d\n",sum-1);    }    return 0;}



1 0
原创粉丝点击