并查集(路径压缩) 畅通工程 HDU1232
树这种数据结构容易出现极端情况,因为在建树的过程中,树的最终形态严重依赖于输入数据本身的性质,比如数据是否排序,是否随机分布等等。比如在输入数据是有序的情况下,构造的BST会退化成一个链表。(BST可以演变成为红黑树或者AVL树等来克服。)
但当我们仅需要连通与否的信息时,层层查找的效率就会很低,这时可以改变树中的多层关系,将树的结构扁平化。
并查集的工作原理如下:
这样查询白面葫芦娃和仙子狗尾巴花是否连通,就省去很多遍历。
树的扁平化由查询函数find完成:
1 int find(int p){
2 while (p != id[p]){
3 // 将p节点的父节点设置为它的爷爷节点
4 id[p] = id[id[p]];
5 p = id[p];
6 }
7 return p;
8 }
HDU1232 畅通工程 题意:
在地图上有若干个城镇(看作点),城镇之间是有道路直接相连的。最后要解决的是整幅图的连通性问题。比如随意给你两个点,让你判断它们是否连通,或者问你整幅图一共有几个连通分支,也就是被分成了几个互相独立的块。还需要修几条路,实质就是求有几个连通分支。
通过代码详细解释:
1 #include<stdio.h> 2 int p[1000]; //用于描述没个数的上一级,保存当前坐标的上一级,上司 3 int find(int x){//用于查找 x 的老大,掌门人 4 int r=x,t,i,j; 5 while(p[r]!=r) //最后r即使x的老大 6 r=p[r]; 7 t=x; 8 while(p[t]!=r){ //这是利用已知的老大的坐标,压缩路径,将老大所有的手下的的手下的手下.....全部归到只是老大的下一级,为了方便各个小弟查询老大 9 i=p[t]; 10 p[t]=r; 11 t=i; 12 } 13 return r;//返回老大的坐标 14 } 15 bool myunion(int x,int y){//用于将两个集合合并在一起 16 if(find(x)!=find(y)){//查询两个数的老大,如果不同老大,将x的老大变成y的老大 17 p[find(y)]=p[find(x)]; 18 return true; //返回1用于题目计算需要建的路(打架的次数) 19 } 20 return false; 21 } 22 int main(){ 23 int n,m,x,y,i; 24 while(~scanf("%d%d",&n,&m)&&n){ 25 int s=n-1;//开始若有n个点,则需要修建n-1条路(打n-1次架) 26 for(i=1;i<=n;i++)//初始化,把所有点都初始化成自己是自己的老大,自成一派 27 p[i]=i; 28 for(i=1;i<=m;i++){ 29 scanf("%d%d",&x,&y); 30 if(myunion(x,y))s--;//返回1 则表示两个不同小弟是不同的老大,打了一架后,则需要打s-1次架。 31 } 32 /*计算s还可以用,此时每个门派(集合)都归纳好了,开始看看有多少个不同的集合,即看看有多少个不同的老大,根据有多少个老大,这是在开始看看要打多少次架(帮派间的斗争!!)有n个老大就要打n-1次架 (有n个集合就要n-1次修路)可以用一个数组存储根,在查询有多少个根! */ 33 printf("%d\n",s); 34 } 35 }
union函数有两种实现方法,其中快速合并的图解:
Quick-Union 算法和Quick-Find只有find和union两个方法有所不同:
1 int find(int p){ 2 // 寻找p节点所在组的根节点,根节点具有性质id[root] = root 3 while (p != id[p]) p = id[p]; 4 return p; 5 } 6 void union(int p, int q){ 7 // Give p and q the same root. 8 int pRoot = find(p); 9 int qRoot = find(q); 10 if (pRoot == qRoot) 11 return; 12 id[pRoot] = qRoot; // 将一颗树(即一个组)变成另外一课树(即一个组)的子树 13 count--; 14 }