• 并查集


    并查集

    一.并查集的两个功能

    1.查询一个集合中的一个元素(或者说查询某个元素在哪个集合中)

    2.合并两个集合

    二.优化

    1.按集合的思想来看,并查集其实不是一个树形结构,就是一个普通的集合,查询是O(1),合并是O(n),必须扫描所有元素;

       树形结构是优化出来的,下面的内容都是树形结构的,集合的那种就不说了,因为我们平时看到的并查集都是树形结构的

    2.优化主要是两个,一个是查询的优化——压缩路径,一边查询一边修改,这个优化足够强大了。

      压缩路径:原本的树形可能是无规则的,最坏的条件是一条链状的,查询时就是O(n),压缩路径的原理很简单,当某一次查询一个元素x的时候,从x回到祖先的路径中所有元素都做  修改,把他们的双亲p[i]都修改为祖先,也就是直接和祖先相连,那么修改后的结果就是

    一种树形结构,树根(祖先)单独在一层,它的所有孩子都在同一层,即直接与树根相连,整棵树的层数是2,那么查询任何一个顶点的祖先时时间复杂度就是0(1)

    在给出优化之前先给出两种查询代码,一种是递归式,一种是迭代式,路径压缩两种都可以

    //递归式
    int find(int x)
    { return p[x]==x ? x : p[x]=find(p[x]);  }
    
    
    //迭代式(前部分是查询,后部分是路径压缩)
    int find(int x)  //迭代式
    {
        int i=x,r=x,j;
        while(r!=p[r])  //查询
            r=p[r];
        while(i!=r)  //路径压缩
        {
            j=p[i];  //先记录下i的双亲
            p[i]=r;  //修改i的直接与祖先相连
            i=j;     //接下来修改原本的双亲
        }
        return r;  //r就是祖先
    }

    代码的话很容易懂,没什么好说的,注释都写了

     

    3.合并优化(启发式)

    合并的话,知道两个集合的祖先a和b,也就是树根a和b,然后以a作为b的孩子连接上b上,或者b作为a的孩子连接在a上,这个是普通的

     这个优化是基于树的结构,基本上很多树的合并都可以用这种思想。现在有两个集合需要合并,那么我们要知道两课树的高度,然后将高度小的合并在高度大的上面,为什么呢?因为这样子合并后的树的高度还是不变,还是原来比较高的那颗树的高度;如果两颗树的高度相同,那么哪个连在那个的上面都可以,但是注意一点,连接后,树的高度加1,为什么呢?很简单啊,两棵树高度相同,一个树根作为另一个树根的孩子连接上去了,那不就是新树增加了一层

    merge3(a,b)
    {
         if (height(a) == height(b))
         {
               height(a) = height(a) + 1;
               set[b] = a; 
          }
         else if (height(a) < height(b))
              set[a] = b;
         else  
              set[b] = a;  
    }

     

    三.并查集的一些常用技巧

    1.并查集判断图连通,或者找出图有多少个连通分量

    并查集初始化话for(int i=1; i<=n; i++) p[i]=i;

    结束后,扫描一遍p数组,看有多少个p[i]=i,其实p[i]=i,这个i就是某个连通分量的祖先,如果有超过一个祖先,那么就是有超过一个连通分量,就是图不连通,所以要找多少个连通分量的话也就是找祖先个数

    2.判断某个连通分量是否成环

    给你一条边(u,v),找到他们的祖先x,y,如果x和y相同,那么他们成环了,因为他们有共同祖先,也就是都有路径到这个共同祖先,而且他们两者本身也有边相连,成环。所以最小生成树kruskal算法中,就是利用这个性质来判断安全边的,凡是能成环的边都是非安全边,都舍去

    3.统计一个连通分量中有多少个元素,需要再加一个数组c

    初始化for(int i=1; i<=n; i++) c[i]=0;

    然后并查集,结束后扫描整个顶点数组,找到每个顶点的祖先x,那么c[x]++;

    当然这样比较慢,我们可以一边合并一边统计

    初始化for(int i=1; i<=n; i++) c[i]=1;    一开始每个顶点都是独立,都是一个集合,集合的元素个数都是1

    然后每次合并两个集合a,b,若是将b并到了a中,那么c[a]=c[a]+c[b],即每次合并都马上更新新的集合中的元素个数

    注意c[i]的意思是 并不是说i所处的集合的元素个数  ,  而是说,当i是一个集合的祖先(树根)时,它能代表这个集合的元素个数,那么c[i]才是这个集合的元素个数。所以说集合{1,5,8,10,12},假如说1才是树根,那么c[1]=5,其余的c[5],c[8],c[10],c[12]并不是

  • 相关阅读:
    Docker & ASP.NET Core (3):发布镜像
    Docker & ASP.NET Core (2):定制Docker镜像
    Docker & ASP.NET Core (1):把代码连接到容器
    Redis五大数据类型的常用操作
    centos安装Redis
    SpringBoot进阶教程(五十一)Linux部署Quartz
    mybatis在xml文件中处理转义字符
    org.apache.ibatis.builder.IncompleteElementException: Could not find result map java.lang.Integer
    SpringBoot进阶教程(五十)集成RabbitMQ---MQ实战演练
    浅谈RabbitMQ Management
  • 原文地址:https://www.cnblogs.com/scau20110726/p/2784915.html
Copyright © 2020-2023  润新知