并查集基础(2018.1.20)

之前似乎也写过关于并查集的一些东西,不过似乎比较早了,今天重新梳理了一下。

在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。

这样的问题看起来似乎很简单,每次直接暴力查找即可,但是我们需要注意的问题是,在数据量非常大的情况下,那么时间复杂度将达到O(N*n)(n为查询次数),那么这类问题在实际应用中,如果采取上述方法去做的话,耗费的时间将是巨大的。而如果用常规的数据结构去解决该类问题的话(顺序结构,普通树结构等),那么计算机在空间上也无法承受。所以,并查集这种数据结构便应运而生了。

并查集:

并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。

           合并集合和查找集合中元素。

操作:三个

1.初始化
把每个点所在集合初始化为其自身。
通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为O(N)。
包括对所有单个的数据建立一个单独的集合(即根据题目的意思自己建立的最多可能有的集合,为下面的合并查找操作提供操作对象)
        在每一个单个的集合里面,有三个东西。
1,集合所代表的数据。(这个初始值根据需要自己定义,不固定)
2,这个集合的层次通常用rank表示(一般来说,初始化的工作之一就是将每一个集合里的rank置为0)。
      3,这个集合的类别parent(有的人也喜欢用set表示)(其实就是一个指针,用来指示这个集合属于那一类,合并过后的集合,他们的parent指 向的最终值一定是相同的。)
   初始化的时候,一个集合的parent都是这个集合自己的标号。
int set[max];//集合index的类别,或者用parent表示
int rank[max];//集合index的层次,通常初始化为0
int data[max];//集合index的数据类型
//初始化集合void Make_Set(int i){ set[i]=i;//初始化的时候,一个集合的parent都是这个集合自己的标号。没有跟它同类的集合,那么这个集合的源头只能是自己了。 rank[i]=0;}

2.查找
查找元素所在的集合,即根节点。
就是找到parent指针的源头,可以把函数命名为get_parent(或者find_set,这个随你喜欢,以便于理解为主)
       如果集合的parent等于集合的编号(即还没有被合并或者没有同类),那么自然返回自身编号。
       如果不同(即经过合并操作后指针指向了源头(合并后选出的rank高的集合))那么就可以调用递归函数,如下面的代码:
//查找集合i(一个元素是一个集合)的源头(递归实现)
int Find_Set(int i)
{ 
    //如果集合i的父亲是自己,说明自己就是源头,返回自己的标号
   if(set[i]==i)
       return set[i];
    //否则查找集合i的父亲的源头
    return  Find_Set(set[i]);        
}

3.合并
将两个元素所在的集合合并为一个集合。
假设需要合并的两个集合的代表元分别为x和y,则 只需要令parent[x]=y或者parent[y]=x即可。
为了使得合并后的树不产生退化,即   使得树中左右子树的深度差尽可能的小,
对每一个元素x,维护rank[x]为以x为根的子树的深度。合并时,如果rank[x]<rank[y],则
令parent[x]=y,否则令parent[y]=x。
通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。
void Union(int i,int j)
{
    i=Find_Set(i);
    j=Find_Set(j);
    if(i==j) return ;
    if(rank[i]>rank[j]) set[j]=i;
    else
    {
        if(rank[i]==rank[j]) rank[j]++;   
        set[i]=j;
    }
}

小希的迷宫
Problem Description
上次Gardon的迷宫城堡小希玩了很久(见Problem B),现在她也想设计一个迷宫让Gardon来走。但是她设计迷宫的思路不一样,首先她认为所有的通道都应该是双向连通的,就是说如果有一个通道连通了房间A和B,那么既可以通过它从房间A走到房间B,也可以通过它从房间B走到房间A,为了提高难度,小希希望任意两个房间有且仅有一条路径可以相通(除非走了回头路)。小希现在把她的设计图给你,让你帮忙判断她的设计图是否符合她的设计思路。比如下面的例子,前两个是符合条件的,但是最后一个却有两种方法从5到达8。 

 

Input
输入包含多组数据,每组数据是一个以0 0结尾的整数对列表,表示了一条通道连接的两个房间的编号。房间的编号至少为1,且不超过100000。每两组数据之间有一个空行。 
整个文件以两个-1结尾。
 

Output
对于输入的每一组数据,输出仅包括一行。如果该迷宫符合小希的思路,那么输出"Yes",否则输出"No"。
 

Sample Input
   
   
6 8 5 3 5 2 6 4 5 6 0 0 8 1 7 3 6 2 8 9 7 5 7 4 7 8 7 6 0 0 3 8 6 8 6 4 5 3 5 6 5 2 0 0 -1 -1
 

Sample Output
   
   
Yes Yes No
 

Author
Gardon
 

Source
 

Recommend
lxj
 

分析:

        如果给的图是一个树(且输入中不能有重复边输入。如果有,也应该输出No),

         那么输出Yes,否则输出No。

        一个树满足下面两个条件:只有1个连通分量且任意两点间不存在两条路。

        判断任意两点间是否存在两条路只要看已经连通的两点是否还会插入另外一条边进去?

        注意:对于输入数据0 0需要特殊判断。

AC代码:

[cpp]  view plain  copy
  1. #include<cstdio>  
  2. #include<set>  
  3. #include<cstring>  
  4. using namespace std;  
  5. const int maxn=100000+5;  
  6.   
  7. //并查集  
  8. int fa[maxn];  
  9. int findset(int x)  
  10. {  
  11.     return fa[x]==-1? x: fa[x]=findset(fa[x]);  
  12. }  
  13. bool bind(int u,int v)  
  14. {  
  15.     int fu=findset(u);  
  16.     int fv=findset(v);  
  17.     if(fu!=fv)  
  18.     {  
  19.         fa[fu]=fv;  
  20.         return true;  
  21.     }  
  22.     return false;  
  23. }  
  24.   
  25.   
  26. int main()  
  27. {  
  28.     int u,v;  
  29.     while(scanf("%d%d",&u,&v)==2 && u>=0)  
  30.     {  
  31.         if(u==0 && v==0)  
  32.         {  
  33.             printf("Yes\n");  
  34.             continue;  
  35.         }  
  36.   
  37.         memset(fa,-1,sizeof(fa));  
  38.         set<int> st;//st保存所有出现过得整数节点  
  39.         bool ok=true;//表示当前迷宫是否合法  
  40.   
  41.         do  
  42.         {  
  43.             if(!bind(u,v)) ok=false;//连通了多余的边  
  44.             st.insert(u);  
  45.             st.insert(v);  
  46.         }while(scanf("%d%d",&u,&v)==2 && u);  
  47.   
  48.         if(ok)  
  49.         {  
  50.             int cnt=0;//连通分量数目  
  51.             for(set<int>::iterator it=st.begin(); it!=st.end(); ++it)  
  52.             {  
  53.                 if(*it == findset(*it) )  
  54.                     cnt++;  
  55.             }  
  56.             if(cnt>1) ok=false;  
  57.         }  
  58.   
  59.         printf("%s\n",ok?"Yes":"No");  
  60.     }  
  61.     return 0;  
  62. }  


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值