数据结构 之 并查集

并查集是一种树型的数据结构,其保持着用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。

有一个联合-查找算法union-find algorithm)定义了两个操作用于此数据结构:

  • Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
  • Union:将两个子集合并成同一个集合。
下面代码实现一些并查集中的一些基本操作:
一、并查集的初始化
假设现在有一个全集合为S= {0,1,2,3,4,5,6,7,8,9} ,初始化时,将每一元素都划分成为一个单元素的集合。
如下图所示:
这样就初始化出10个单元素集合,每一个集合元素的parent值都是-1.

代码实现过程:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int parent[100];  
  2.   
  3. //构造一个初始并查集,s是集合元素的个数,此时初始化了s个集合,每一个集合都只有一个元素  
  4. //数组的范围为parent[0]~parent[s-1]  
  5. //初始的时候,数组元素的值都是 -1,表示此时都是根结点。  
  6. void ufset(int s)  
  7. {  
  8.     int si=s;  
  9.     for(int i=0;i<si;i++)  
  10.     parent[i]=-1;  
  11. }  

二、 合并Union
所谓合并,即是将两个不相交的集合合并成为一个集合,这个过程将一个集合的parent修改成另一集合的parent。
代码实现过程:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //集合的合并  
  2. //让root2的父指针指向root1即可实现两个集合的合并  
  3. void Union(int root2,int root1)  
  4. {  
  5.     parent[root1]+=parent[root2];  
  6.     parent[root2]=root1;  
  7. }  

举个例子:初始化10个元素(如上图所示),进行下面的合并过程:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. ufset(10);  //初始化10个元素  
  2.   
  3.     Union(6,0);  
  4.     Union(7,0);  
  5.     Union(8,0);  
  6.     Union(4,1);  
  7.     Union(9,1);  
  8.     Union(3,2);  
  9.     Union(5,2);  

合并之后的结果:


但是进行这种合并之后,可能会出现一种不好的效果。如下图:

我们称之为一棵退化的树。

那么如何进行改进呢?我们可以使用加权规则进行合并。也就是将集合元素少的归到集合元素多的中去。
例如:

下面给出代码实现:(注意下面这个方法存在错误,请看下面第二个方法,已修正错误。)
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //使用加权规则得到改进的Union操作  
  2. void WeightUnion(int root2,int root1)  
  3. {  
  4.     int r2=Find(root2);  //r2和r1是root2和root1的父结点  
  5.     int r1=Find(root1);  
  6.     int temp;  
  7.     if(r1!=r2)  
  8.     {  
  9.         temp=parent[r1]+parent[r2];  
  10.         if(parent[r1]<parent[r2]){parent[r1]=temp;parent[r2]=r1;}  //以r2根的树结点多  
  11.         else {parent[r1]=r2;parent[r2]=temp;}  
  12.     }  
  13. }  

错误提醒:很感谢某位网友指出我上面段代码的错误,由于一时疏忽上面WeightUnion这个方法有点错误!修改如下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //使用加权规则得到改进的Union操作  
  2. void WeightUnion(int root2,int root1)  
  3. {  
  4.     int r2=Find(root2);  //r2和r1是root2和root1的父结点  
  5.     int r1=Find(root1);  
  6.     int temp;  
  7.     temp=parent[r1]+parent[r2];  
  8.     if(parent[r1]<=parent[r2])   
  9.     //注意:parent[r1] 和 parent[r2] 都是负数,所以小者其绝对值大,那么拥有的结点数就多  
  10.     //这里就说明 r1为根的树结点  不少于  r2为根的树结点  
  11.     {    
  12.         parent[r1]=temp;    //以r1为根   
  13.         parent[r2]=r1;    
  14.     }    
  15.     else    
  16.     {    
  17.         parent[r1]=r2;    
  18.         parent[r2]=temp;    
  19.     }      
  20. }  



三、查找Find

我们知道,只有当查找到的元素的parent值为负数(此时集合元素个数用这个负数表示),才表示找到根。
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //查找元素x所在集合  
  2. //从x开始,沿父指针链一直向上,直到向上,直到达到一个父指针域为负值的结点位置  
  3. int Find(int x) //迭代查找方式  
  4. {  
  5.     while(parent[x]>=0) x=parent[x];  
  6.     return x;  
  7. }  
  8. /* 
  9. int find_1(int x)  //递归查找方式 
  10. { 
  11.     if(parent[x]<0) return x;  //x是根时,直接返回x 
  12.     else return find_1(parent[x]);  //否则,递归找x的父的根 
  13. } 
  14. */  

进一步优化,在查找过程中可以采用折叠规则压缩路径。


实现的代码:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //折叠规则压缩路径法  
  2. //包含元素i的树中搜索根,并将从元素i到根的路径上的所有结点都变成根的结点  
  3. int collapsingfind(int i)  
  4. {  
  5.     int j;  
  6.     for(j=i;parent[j]>=0;j=parent[j]); //搜索j的根  
  7.     while(i!=j)   //向上逐次压缩  
  8.     {  
  9.         int temp=parent[i];  
  10.         parent[i]=j;  
  11.         i=temp;  
  12.     }  
  13.     return j;  //返回根  
  14. }  


附:上面代码的完整版:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. int parent[100];  
  5.   
  6. //构造一个初始并查集,s是集合元素的个数,此时初始化了s个集合,每一个集合都只有一个元素  
  7. //数组的范围为parent[0]~parent[s-1]  
  8. //初始的时候,数组元素的值都是 -1,表示此时都是根结点。  
  9. void ufset(int s)  
  10. {  
  11.     int si=s;  
  12.     for(int i=0;i<si;i++)  
  13.     parent[i]=-1;  
  14. }  
  15.   
  16. //查找元素x所在集合  
  17. //从x开始,沿父指针链一直向上,直到向上,直到达到一个父指针域为负值的结点位置  
  18. int Find(int x) //迭代查找方式  
  19. {  
  20.     while(parent[x]>=0) x=parent[x];  
  21.     return x;  
  22. }  
  23. /* 
  24. int find_1(int x)  //递归查找方式 
  25. { 
  26.     if(parent[x]<0) return x;  //x是根时,直接返回x 
  27.     else return find_1(parent[x]);  //否则,递归找x的父的根 
  28. } 
  29. */  
  30.   
  31. //折叠规则压缩路径法  
  32. //包含元素i的树中搜索根,并将从元素i到根的路径上的所有结点都变成根的结点  
  33. int collapsingfind(int i)  
  34. {  
  35.     int j;  
  36.     for(j=i;parent[j]>=0;j=parent[j]); //搜索j的根  
  37.     while(i!=j)   //向上逐次压缩  
  38.     {  
  39.         int temp=parent[i];  
  40.         parent[i]=j;  
  41.         i=temp;  
  42.     }  
  43.     return j;  //返回根  
  44. }  
  45.   
  46. //集合的合并  
  47. //让root2的父指针指向root1即可实现两个集合的合并  
  48. void Union(int root2,int root1)  
  49. {  
  50.     parent[root1]+=parent[root2];  
  51.     parent[root2]=root1;  
  52. }  
  53.   
  54. //使用加权规则得到改进的Union操作  
  55. void WeightUnion(int root2,int root1)  
  56. {  
  57.     int r2=Find(root2);  //r2和r1是root2和root1的父结点  
  58.     int r1=Find(root1);  
  59.     int temp;  
  60.     if(r1!=r2)  
  61.     {  
  62.         temp=parent[r1]+parent[r2];  
  63.         if(parent[r1]<parent[r2]){parent[r1]=temp;parent[r2]=r1;}  //以r2根的树结点多  
  64.         else {parent[r1]=r2;parent[r2]=temp;}  
  65.     }  
  66. }  
  67.   
  68. int main()  
  69. {  
  70.     ufset(10);  //初始化10个元素  
  71.   
  72.     Union(6,0);  
  73.     Union(7,0);  
  74.     Union(8,0);  
  75.     Union(4,1);  
  76.     Union(9,1);  
  77.     Union(3,2);  
  78.     Union(5,2);  
  79.   
  80.     for(int i=0;i<10;i++) cout<<parent[i]<<" ";  
  81.     return 0;  
  82. }  
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值