并查集


一、并查集定义、基本操作、路径压缩

并查集性质:并查集产生的每一个集合都是一棵树

1、并查集定义及初始化

//用一个数组实现并查集
int father[N];
//初始化
for(int i=1;i<=N;i++)
{
    father[i]=i;
}

 

2、查找

规定同一个集合中只存在一个根结点,因此查找操作即对给定的结点寻找其根结点的过程。实现方式可以是递归或递推

①递推,findFather函数返回元素x所在集合的根结点

int findFather(int x)
{
    while(x!=father[x]){
        x=father[x];   //获取自己的父亲结点
    }
    return x;
}

②递归

int findFather(int x)
{
    if(x==father[x]) return x;  //如果找到根结点,则返回根结点编号x
    else return findFather(father[x]); //否则递归判断x的父亲结点是否是根结点
}

 

3、合并

把给定的两个元素所在的集合合并
思路:
①对于给定的两个元素a、b,判断它们是否属于同一个集合,可以调用上面的查找函数,对这两个元素a,b分别查找根结点,然后判断其根结点是否相同。
②合并两个集合:在①中已经获得两个元素的根结点faA和faB,因此只要把其中一个的父亲结点指向另一个结点,例如father[faA]=faB或者father[faB]=faA。

void Union(int a,int b)
{
    int faA=findFather(a); //查找a的根结点,记为faA
    int faB=findFather(b); //查找b的根结点,记为faB
    if(faA!=faB){          //不属于同一个集合
        father[faA]=faB;   //合并它们
    }
}

 

4、路径压缩

把当前查询结点的路径上的所有结点的父亲都指向根结点,查询复杂度降为O(1)
思路:
①按原先的写法获得x的根结点r
②重新从x开始走一遍寻找根结点的过程,把路径上经过的所有结点的父亲全部改为根结点r

int findFather(int x){
    //由于x在下面的while种会变成根结点,因此先把原先的x保存一下
    int a=x;
    while(x!=father[x]){    //寻找根结点
        x=father[x];
    }
    //到这里,x存放的是根结点,下面吧路径上的所有结点的father都改为根结点
    while(a!=father[a])
    {
        int z=a;         //因为a要被father[a]覆盖,所以先保存a的值,以修改father[a]
        a=father[a];     //a回溯父亲结点
        father[z]=x;     //将原先的结点a的父亲结点改为根结点x
    }
    return x;            //返回根结点
}

递归写法

int findFather(int v){
    if(v==father[v]) return v;    //找到根结点
    else{
        int F=findFather(father[v]);  //递归寻找father[v]的根结点
        father[v]=F;      //将根结点F赋值给father[v]
        return F;         //返回根结点F
    }
}

 

二、练习题

HDU 1856

题意:有编号为1到10^7的男生在一个房间,现在给出n组数据,每组数据包括两个编号,且这两个编号的人是朋友,如果A和B是朋友,B和C是朋友,那么A,B,C三人都是朋友,哪一个朋友圈中人数最多,输出这个朋友圈的人数;

 思路:并查集思想,多个集合中元素最多的即为所求;可看做求每个集合的元素数目,这里借助num数组,存储以该结点作为根结点时的集合元素总数,初始值均为1,可看做每个元素最初单独构成一个集合,在合并两个不在同一集合的元素时,更新根结点的num值,并比较更新后的num值和maxm,maxm总是取当前各个集合元素的最大值。最后输出maxm即为所求。这里数据量较大,容易时间超限,故用路径压缩

#include 
#include 
using namespace std;
#define maxn 10000005
int father[maxn],num[maxn],maxm;
void Initial()
{
    for(int i=1;i<=maxn;i++){
        father[i]=i;
        num[i]=1;
    }
}
int findFather(int x)
{
    int a=x;
    while(x!=father[x])
    {
        x=father[x];
    }
    //路径压缩
    while(a!=father[a])
    {
        int d=a;
        a=father[a];
        father[d]=x;
    }
    return x;
}
void Union(int a,int b)
{
    int faA=findFather(a);
    int faB=findFather(b);
    if(faA!=faB){
        father[faA]=faB;
        num[faB]+=num[faA];
        maxm=max(maxm,num[faB]);   
    }
}
int main()
{
    int n,var1,var2;
    while(cin>>n)
    {
        Initial();
        maxm=0;
        if(n==0){
            printf("1\n");
            continue;
        }
        for(int i=0;i)
        {
            scanf("%d %d",&var1,&var2);
            Union(var1,var2);
        }
        cout<endl;
    }
    return 0;
}

问题 A: 通信系统

题目描述

某市计划建设一个通信系统。按照规划,这个系统包含若干端点,这些端点由通信线缆链接。消息可以在任何一个端点产生,并且只能通过线缆传送。每个端点接收消息后会将消息传送到与其相连的端点,除了那个消息发送过来的端点。如果某个端点是产生消息的端点,那么消息将被传送到与其相连的每一个端点。
为了提高传送效率和节约资源,要求当消息在某个端点生成后,其余各个端点均能接收到消息,并且每个端点均不会重复收到消息。
现给你通信系统的描述,你能判断此系统是否符合以上要求吗?

输入

输入包含多组测试数据。每两组输入数据之间由空行分隔。
每组输入首先包含2个整数N和M,N(1<=N<=1000)表示端点个数,M(0<=M<=N*(N-1)/2)表示通信线路个数。
接下来M行每行输入2个整数A和B(1<=A,B<=N),表示端点A和B由一条通信线缆相连。两个端点之间至多由一条线缆直接相连,并且没有将某个端点与其自己相连的线缆。
当N和M都为0时,输入结束。

输出

对于每组输入,如果所给的系统描述符合题目要求,则输出Yes,否则输出No。

样例输入 http://codeup.cn/problem.php?cid=100000615&pid=1

#include 
#include 
#include <set>
using namespace std;
#define maxn 1005
int father[maxn];
void Initial(int n)
{
    for(int i=1;i<=n;i++) father[i]=i;
}
int findFather(int x)
{
    int a=x;
    while(x!=father[x]){
        x=father[x];
    }
    while(a!=father[a])
    {
        int z=a;
        a=father[a];
        father[z]=x;
    }
    return x;
}
void Union(int a,int b)
{
    int faA=findFather(a);
    int faB=findFather(b);
    if(faA!=faB){
        father[faA]=faB;
    }
}
int main()
{
    int n,m;
    int var1,var2;
    while(cin>>n)
    {
        if(n==0) break;
        cin>>m;
        set<int> st;
        Initial(maxn);
        for(int i=0;i)
        {
            cin>>var1>>var2;
            Union(var1,var2);
        }
        for(int i=1;i<=n;i++)
        {
            findFather(i);
            st.insert(father[i]);
        }
        cout<1<<endl;
    }
    return 0;
}

 

问题C:How Many Tables

http://codeup.cn/problem.php?cid=100000615&pid=2

#include 
#include 
#include <set>
using namespace std;
#define maxn 1005
int father[maxn];
void Initial(int n)
{
    for(int i=1;i<=n;i++) father[i]=i;
}
int findFather(int x)
{
    int a=x;
    while(x!=father[x]){
        x=father[x];
    }
    while(a!=father[a])
    {
        int z=a;
        a=father[a];
        father[z]=x;
    }
    return x;
}
void Union(int a,int b)
{
    int faA=findFather(a);
    int faB=findFather(b);
    if(faA!=faB){
        father[faA]=faB;
    }
}
int main()
{
    int n,m,t;
    int var1,var2;
    cin>>t;
    while(t>0)
    {
        cin>>n>>m;
        set<int> st;
        Initial(maxn);
        for(int i=0;i)
        {
            cin>>var1>>var2;
            Union(var1,var2);
        }
        for(int i=1;i<=n;i++)
        {
            findFather(i);
            st.insert(father[i]);
        }
        cout<endl;
        t--;
    }
    return 0;
}