【模板】并查集

并查集

并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。——以上摘自360百科。

并查集并查集,字面意思来看完成的也主要就是三个操作:合并,查询,集合。

其主要操作,就是为了判断某两个元素是否在同一个集合里面,而一个一个的寻找枚举由太过复杂,于是便有了并查集这个方便的思想:寻找祖宗。若两个元素的祖宗节点是相同的,那么这两个元素也必定属于同一个集合。

首先来看一个例题:

题目背景

若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。

题目描述

规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。

输入输出格式

输入格式:

 

第一行:三个整数n,m,p,(n<=5000,m<=5000,p<=5000),分别表示有n个人,m个亲戚关系,询问p对亲戚关系。

以下m行:每行两个数Mi,Mj,1<=Mi,Mj<=N,表示Mi和Mj具有亲戚关系。

接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。

 

输出格式:

P行,每行一个’Yes’或’No’。表示第i个询问的答案为“具有”或“不具有”亲戚关系。

 

begin::那么这个显然这就是一个十分经典的并查集模板题了。

若是不用并查集,而是按照传统的思路来看,首先必须要保存m条边,然后再次进行普通的遍历手法,时间复杂度大约在O(n^2)左右,效率很低。

由此想到了运用并查集:

首先便是对于并查集的第一项工作:怎么实现连通块??

将每个人抽象成为一个点,编号分别为1,2,3,.....n,运用集合的思路,对于每个人建立一个集合,此时每一个集合里都是只有这个人本身,也可以理解为:自己就是自己的祖宗。

之后每次初选一个亲戚关系时,就将他们合并起来,如果之后又询问,就在当前结果中看两个元素是否处于同一个集合就可以了。

那么接下来就是第二个问题:如何判断两个集合是否有亲戚关系??

这就是之前所说的:找祖宗,然后看两个集合的祖宗是不是相同就可以了。

下面附上代码::

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstdlib>
#define MAXN 1000001
using namespace std;
int f[MAXN];//指的是某一位的父亲节点 
int n,m;//n:总人数 m:已知关系数量 
int x,y;//亲戚双方 
int q;//询问次数 
int i;//用于循环 
int find(int x)//寻找x的祖宗 递归版 (普通版)
{
    if(f[x]!=x)
        return find(f[x]);
    else 
        return f[x];    
} 
int find_2(int x)//递归版 (普通版) 
{
    while(f[x]!=x)
        x=f[x];
    return x;
}
int find_imp(int x)//寻找x的祖宗(路径压缩改进版) (递归版)
{
    if(f[x]!=x)
        return f[x]=find_imp(f[x]);//路径压缩,改进的核心 
    else return f[x];
}
int find_imp_2(int x)//寻找x的祖宗(路径压缩改进版) (非递归版)
{
    while(f[x]!=x)
        x=f[x];
    return x;
}
void unionn(int r1,int r2)//合并 r1,r2
{
    f[r2]=r1;
}
int judge(int x,int y)//判断x y是否处于同一集合
{
    int Auto1=find_imp(x);
    int Auto2=find_imp(y);
    if(Auto1==Auto2)
        return true;
    else
        return false;
 } 
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        f[i]=i;//每个点的父亲是其本身,即该集合中只有其本身一个
    for(int i=1;i<=m;i++)
    {
        cin>>x>>y;
        int r1=find_imp(x);
        int r2=find_imp(y);
        if(r1!=r2)
            unionn(x,y);
     } 
     cin>>q;
     for(int i=1;i<=q;i++)
     {
         cin>>x>>y;
         if(judge(x,y)==1)
         cout<<"Yes";
         else cout<<"No";
     }
     return 0;
}
posted @ 2018-03-04 21:14  Sue_Shallow  阅读(228)  评论(0编辑  收藏  举报
Live2D