• 笔试算法题(38):并查集(Union-Find Sets)


    议题:并查集(Union-Find Sets)

    分析:

    • 一种树型数据结构,用于处理不相交集合(Disjoint Sets)的合并以及查询;一开始让所有元素独立成树,也就是只有根节点的树;然后根据需要将关联的元素(树)进行合并;合并的方式仅仅是将一棵树最原始的节点的父亲索引指向另一棵树;

    • 优化:加入一个rank数组存储节点深度的下界(从当前节点到其最远子节点的距离),从而可以启发式的对树进行合并,从而减少树的深度,防止树的退化;使 得包含较少节点的树根指向包含较多节点的树根,具体指代为树的高度;另一个优化就是路径压缩,尽可能将子节点都直接连接到根节点之后;

    • 并查集的空间复杂度为O(N),构建一个集合的时间复杂度为O(N);压缩后的查找复杂度是一个很小的常数;应用:Kruskal算法求最小生成树中判断新加入的边是否在同一棵树内部;两个节点的最近公共祖先(Least Common Ancestors);

    • 初始化father:各个节点独立成树,并且其father[i]=i,也就是其父节点就是其自身;

      father[i]
      0 1 2 3 4 5 6 7 8 9
      0 1 2 3 4 5 6 7 8 9

      初始化rank:各个节点为根节点,所以高度都为1;
      rank[i]
      0 1 2 3 4 5 6 7 8 9
      1 1 1 1 1 1 1 1 1 1

      合并2和6:由于rank[2]=rank[6],所以将2的父亲索引指向6,这样2和6就在同一棵树;并需将6的rank值增加1;
      father[i]
      0 1 2 3 4 5 6 7 8 9
      0 1 6 3 4 5 6 7 8 9
      rank[i]
      0 1 2 3 4 5 6 7 8 9
      1 1 1 1 1 1 2 1 1 1

    样例:

     1 int *father;
     2 int *rank;
     3 
     4 /**
     5  * 并查集的初始化:
     6  * 数组father中的元素在最开始ide时候都是独立的树,也就是只有根节点
     7  * 的树,数组father的下标i表示节点,而father[i]的值表示i节点的父亲
     8  * 节点;rank[i]=1表示一开始所有树节点的高度都为1
     9  * */
    10 void init(int cap) {
    11         father=new int[cap];
    12         rank=new int[cap];
    13         /**
    14          * 时间复杂度为O(N)
    15          * */
    16         for(int i=0;i<10;i++) {
    17                 father[i]=i;
    18                 rank[i]=1;
    19         }
    20 }
    21 
    22 void clean() {
    23         delete [] father;
    24         delete [] rank;
    25 }
    26 /**
    27  * 查找元素所在的集合并进行路劲压缩:
    28  * 由于需要频繁使用GetFather()函数,并且其时间复杂度受树结构影响;
    29  * 当元素较多的时候,集合退化成链表,则GetFather()需要O(N),所以
    30  * 需要对其进行优化,每次调用GetFather()的时候都将输入元素压缩成
    31  * 最原始父亲节点的直接子节点
    32  * */
    33 int GetFather(int son) {
    34         if(father[son]==son)
    35                 return son;
    36         else {
    37                 father[son]=GetFather(father[son]);
    38                 return father[son];
    39         }
    40 }
    41 
    42 /**
    43  * 合并两个不相交的集合:
    44  * 输入元素x和y来自两个不相交的集合,找到其最原始的父亲节点
    45  * 并将一个原始父亲节点设置为另一个原始父亲节点的父亲节点
    46  * */
    47 void Union1(int x, int y) {
    48         /**
    49          * GetFather()为递归寻找输入节点的最原始的父亲节点
    50          * */
    51         int fx=GetFather(x);
    52         int fy=GetFather(y);
    53         /**
    54          * 判断x和y是否来自同一棵树,如果不是才进行赋值;其实可以
    55          * 不同进行判断(省去if语句)
    56          * 注意最原始父亲节点的father[i]=i;
    57          * */
    58         if(fx!=fy)
    59                 father[fx]=fy;
    60 }
    61 
    62 /**
    63  * 利用rank加权数组启发式进行合并
    64  * */
    65 void Union2(int x, int y) {
    66         int fx=GetFather(x);
    67         int fy=GetFather(y);
    68 
    69         if(fx==fy) return;
    70         /**
    71          * rank[fx]较大,说明其越靠近根节点,则将
    72          * fy连接到其后面可以压缩路径
    73          * */
    74         if(rank[fx]>rank[fy])
    75                 father[fy]=fx;
    76         else {
    77                 if(rank[fx]==rank[fy])
    78                         rank[fy]++;
    79                 father[fx]=fy;
    80         }
    81 }
    82 
    83 /**
    84  * 判断两个元素是否属于同一个集合:
    85  * 利用GetFather()函数判断其最原始父亲节点是否相同
    86  * */
    87 bool IsSameSet(int x, int y) {
    88         return GetFather(x)==GetFather(y);
    89 }
  • 相关阅读:
    makefile中宏定义
    make的静态模式
    makefile中两重if判断
    定义命令包
    嵌套执行make
    AcWing 1014. 登山
    AcWing 482. 合唱队形
    AcWing 1027. 方格取数
    AcWing 1016. 最大上升子序列和
    AcWing 187. 导弹防御系统
  • 原文地址:https://www.cnblogs.com/leo-chen-2014/p/3752378.html
Copyright © 2020-2023  润新知