zoukankan      html  css  js  c++  java
  • 并查集

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

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

    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 <iostream>
    #include <cstdio>
    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
    ");
                continue;
            }
            for(int i=0;i<n;i++)
            {
                scanf("%d %d",&var1,&var2);
                Union(var1,var2);
            }
            cout<<maxm<<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。

    样例输入 

    4 3
    1 2
    2 3
    3 4
    
    3 1
    2 3
    
    0 0

    样例输出

    Yes
    No

    思路:由于题目的限制,若构成的图形成环则直接输出no即可,而根据并查集的性质,并查集一定会把环拆解,即并查集产生的每一个集合都是树,所以一开始不知道怎么判断产生了环,后来想到如果图中边的数目大于顶点数目减一则该图中一定有环,而题目又声明两个端点之间至多由一条线缆直接相连,并且没有将某个端点与其自己相连的线缆。故可以放心用m>n-1判断图中有环,直接Pass掉。

    #include <iostream>
    #include <cstdio>
    #include <set>
    using namespace std;
    #define maxn 1005
    int father[maxn];
    void Initial()
    {
        for(int i=1;i<=maxn;i++) father[i]=i;
    }
    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;
        }
    }
    int main()
    {
        int n,m,var1,var2;
        while(cin>>n>>m)
        {
            Initial();
            set<int> st;
            if(n==0&&m==0) break;
            for(int i=0;i<m;i++)
            {
                cin>>var1>>var2;
                Union(var1,var2);
            }
            if(m>n-1){          //表示一定会构成环
                cout<<"No
    ";
                continue;
            }
            for(int i=1;i<=n;i++)
            {
                findFather(i);
                st.insert(father[i]);
            }
            if(st.size()==1){
                cout<<"Yes
    ";
            }else{
                cout<<"No
    ";
            }
        }
        return 0;
    }

    好朋友

     

    #include <cstdio>
    #include <iostream>
    #include <set>
    using namespace std;
    #define maxn 105
    int father[maxn];
    void Initial(int father[])
    {
        for(int i=1;i<=maxn;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,num=1;
        int var1,var2;
        set<int> st;
        Initial(father);
        cin>>n>>m;
        for(int i=0;i<m;i++)
        {
            cin>>var1>>var2;
            Union(var1,var2);
        }
        for(int i=1;i<=n;i++){
            findFather(i);
            st.insert(father[i]);
        }
        cout<<st.size()<<endl;
        return 0;
    }

     

    问题B:畅通工程

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

    #include <cstdio>
    #include <iostream>
    #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<m;i++)
            {
                cin>>var1>>var2;
                Union(var1,var2);
            }
            for(int i=1;i<=n;i++)
            {
                findFather(i);
                st.insert(father[i]);
            }
            cout<<st.size()-1<<endl;
        }
        return 0;
    }

     

    问题C:How Many Tables

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

    #include <cstdio>
    #include <iostream>
    #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<m;i++)
            {
                cin>>var1>>var2;
                Union(var1,var2);
            }
            for(int i=1;i<=n;i++)
            {
                findFather(i);
                st.insert(father[i]);
            }
            cout<<st.size()<<endl;
            t--;
        }
        return 0;
    }
    天晴了,起飞吧
  • 相关阅读:
    Kalman Filters
    基于堆栈实现计算器
    Centos 7创建软连接,硬连接的方法
    Centos 7下对磁盘的分区格式化挂载交换空间的建立
    文件的归档打包压缩解压缩
    文件目录操作命令及权限的修改设置
    用户添加修改文件的操作
    linux今日学习命令 who
    CentOS安装流程
    计算机网络基础
  • 原文地址:https://www.cnblogs.com/jianqiao123/p/14422750.html
Copyright © 2011-2022 走看看