• [算法] 并查集


    引入

    • 连接问题:一个结点和另一个结点是否相连
      • 网络中节点间的连接状态
      • 微信中任意两个人是否通过好友认识
    • 路径问题:一个结点到另一个结点的路径
    • 集合问题:经常使用并集,需要查询元素是否是一类

         

    操作

    • union(p,q):连接两个元素(放入一个组中)
    • find(p):p在哪个组中
    • isConnected(p,q):两个元素是否相连(在一个组中)

    实现

    • Quick Find:两个数组,一个存储id号,一个存储组号,
      • find(p):查询组号,复杂度O(1)
      • union(p,q):改变元素的组号,复杂度O(n)
      • isConnected(p,q):两个元素组号是否相同,复杂度O(1)
    • Quick Union:两个数组,一个存储id号,一个存储父节点
      • find(p):查找根节点编号,复杂度O(h),h为树的高度
      • union(p,q):改变父节点值,复杂度O(h)
      • isConnected(p,q):两个元素是否有相同的根,复杂度O(h)
    • 基于size优化:将元素少的集合并到元素多的集合中,使得层数更少,查询更快
    • 基于rank优化:将层数少的集合合并到层数多的集合中
    • 路径压缩(path compression):寻找根的时候两步一跳,指向父亲的父亲(时间复杂度近乎O(1))

    示例程序

    • UnionFind1.h:Quick Find
    • UnionFind2.h:Quick Union
    • UnionFind3.h:基于size优化
    • UnionFind4.h:基于rank优化

    main.cpp

     1 #include <iostream>
     2 #include "UnionFindTestHelper.h"
     3 
     4 using namespace std;
     5 
     6 int main(){
     7     int n = 10000;
     8     // O(n2)
     9     UnionFindTestHelper::testUF1(n);
    10     UnionFindTestHelper::testUF2(n);
    11     UnionFindTestHelper::testUF3(n);
    12     UnionFindTestHelper::testUF4(n);
    13     return 0;
    14 }

    UnionFindTestHelper.h

     1 #include <iostream>
     2 #include <ctime>
     3 #include "UnionFind1.h"
     4 #include "UnionFind2.h"
     5 #include "UnionFind3.h"
     6 #include "UnionFind4.h"
     7 
     8 using namespace std;
     9 
    10 namespace UnionFindTestHelper{
    11     void testUF1( int n ){
    12         srand( time(NULL) );
    13         UF1::UnionFind uf = UF1::UnionFind(n);
    14         time_t startTime = clock(); 
    15         // 进行n次操作,每次随机选择两个元素进行合并操作 
    16         for( int i = 0 ; i < n ; i ++ ){
    17             int a = rand()%n;
    18             int b = rand()%n;
    19             uf.unionElements(a,b);
    20         }
    21         // 进行n次操作,每次随机选择两个元素查询他们是否属于一个集合
    22          for(int i = 0 ; i < n ; i ++ ){
    23              int a = rand()%n;
    24              int b = rand()%n;
    25              uf.isConnected(a,b);
    26          }
    27          time_t endTime = clock();
    28         cout << "UF1, "<<2*n<<" ops, "<<double(endTime - startTime)/
    29         CLOCKS_PER_SEC<<" s"<<endl;
    30     }
    31     void testUF2( int n ){
    32         srand( time(NULL) );
    33         UF2::UnionFind uf = UF2::UnionFind(n);
    34         time_t startTime = clock(); 
    35         // 进行n次操作,每次随机选择两个元素进行合并操作 
    36         for( int i = 0 ; i < n ; i ++ ){
    37             int a = rand()%n;
    38             int b = rand()%n;
    39             uf.unionElements(a,b);
    40         }
    41         // 进行n次操作,每次随机选择两个元素查询他们是否属于一个集合
    42          for(int i = 0 ; i < n ; i ++ ){
    43              int a = rand()%n;
    44              int b = rand()%n;
    45              uf.isConnected(a,b);
    46          }
    47          time_t endTime = clock();
    48         cout << "UF2, "<<2*n<<" ops, "<<double(endTime - startTime)/
    49         CLOCKS_PER_SEC<<" s"<<endl;
    50     }
    51     void testUF3( int n ){
    52         srand( time(NULL) );
    53         UF3::UnionFind uf = UF3::UnionFind(n);
    54         time_t startTime = clock(); 
    55         // 进行n次操作,每次随机选择两个元素进行合并操作 
    56         for( int i = 0 ; i < n ; i ++ ){
    57             int a = rand()%n;
    58             int b = rand()%n;
    59             uf.unionElements(a,b);
    60         }
    61         // 进行n次操作,每次随机选择两个元素查询他们是否属于一个集合
    62          for(int i = 0 ; i < n ; i ++ ){
    63              int a = rand()%n;
    64              int b = rand()%n;
    65              uf.isConnected(a,b);
    66          }
    67          time_t endTime = clock();
    68         cout << "UF3, "<<2*n<<" ops, "<<double(endTime - startTime)/
    69         CLOCKS_PER_SEC<<" s"<<endl;
    70     }
    71     void testUF4( int n ){
    72         srand( time(NULL) );
    73         UF4::UnionFind uf = UF4::UnionFind(n);
    74         time_t startTime = clock(); 
    75         // 进行n次操作,每次随机选择两个元素进行合并操作 
    76         for( int i = 0 ; i < n ; i ++ ){
    77             int a = rand()%n;
    78             int b = rand()%n;
    79             uf.unionElements(a,b);
    80         }
    81         // 进行n次操作,每次随机选择两个元素查询他们是否属于一个集合
    82          for(int i = 0 ; i < n ; i ++ ){
    83              int a = rand()%n;
    84              int b = rand()%n;
    85              uf.isConnected(a,b);
    86          }
    87          time_t endTime = clock();
    88         cout << "UF4, "<<2*n<<" ops, "<<double(endTime - startTime)/
    89         CLOCKS_PER_SEC<<" s"<<endl;
    90     }
    91 } 
    View Code

    UnionFind1.h

     1 #include <iostream>
     2 #include <cassert>
     3 
     4 using namespace std;
     5 
     6 namespace UF1{
     7     
     8 class UnionFind{
     9     private:
    10         int* id;
    11         int count;
    12     public:
    13         UnionFind( int n ){
    14             count = n;
    15             id = new int[n];
    16             // 初始化,每个id[d]指向自己 
    17             for( int i = 0 ; i < n ; i ++ )
    18                 id[i] = i;
    19         }
    20     ~UnionFind(){
    21         delete [] id;
    22     }
    23     
    24     // 查找元素p对应的集合编号 
    25     int find( int p ){
    26         assert( p >= 0 && p < count );
    27         return id[p];
    28     }
    29     
    30     // 查看元素p和q是否属于一个集合
    31     // O(1) 
    32     bool isConnected( int p , int q ){
    33         return find(p) == find(q);
    34     }
    35     
    36     // 合并元素p和q所属的集合
    37     // O(n) 
    38     void unionElements( int p, int q ){
    39         int pID = find(p);
    40         int qID = find(q);
    41         
    42         if( pID == qID )
    43             return;
    44         for( int i = 0 ;i < count ; i ++){
    45             if( id[i] == pID )
    46                 id[i] = qID;
    47         }
    48     }
    49 };
    50 }
    View Code

    UnionFind2.h

     1 #include<cassert>
     2 
     3 namespace UF2{
     4     
     5     class UnionFind{
     6         private:
     7             // parent[i]表示第i个元素所指向的父节点 
     8             int* parent;
     9             int count;
    10         public:
    11             UnionFind(int count){
    12                 parent = new int[count];
    13                 this->count = count;
    14                 for( int i = 0 ; i < count ; i ++)
    15                     parent[i] = i;
    16             }
    17             
    18             ~UnionFind(){
    19                 delete[] parent;
    20             } 
    21             
    22             // 查找元素p对应的集合编号
    23             // O(h)复杂度,h为树的高度
    24             int find(int p){
    25                 assert( p >= 0 && p < count );
    26                 // 不断查询父节点,直到根节点
    27                 // 根节点特点:parent[p] = p
    28                 while( p != parent[p] )
    29                     p = parent[p];
    30                 return p; 
    31             }
    32             // 查看元素p和q是否属于一个集合
    33             // O(h)复杂度,h为树的高度
    34             int isConnected( int p , int q ){
    35                 return find(p) == find(q);
    36             }
    37             // 合并p和q所属的集合
    38             void unionElements(int p, int q){
    39                 int pRoot = find(p);
    40                 int qRoot = find(q);
    41                 if( pRoot == qRoot )
    42                     return;
    43                 parent[pRoot] = qRoot;
    44             } 
    45     };
    46     
    47 }
    View Code

    UnionFind3.h

     1 #include<cassert>
     2 
     3 namespace UF3{
     4     
     5     class UnionFind{
     6         private:
     7             int* parent; // parent[i]表示第i个元素所指向的父节点
     8             int* sz; // sz[i]表示以i为根的集合中的元素个数 
     9             int count;  // 数据个数 
    10             
    11         public:
    12             UnionFind(int count){
    13                 parent = new int[count];
    14                 sz = new int[count]; 
    15                 this->count = count;
    16                 for( int i = 0 ; i < count ; i ++){ 
    17                     parent[i] = i;
    18                     sz[i] = 1;
    19                 } 
    20             }
    21             
    22             ~UnionFind(){
    23                 delete[] parent;
    24                 delete[] sz;
    25             } 
    26             
    27             // 查找元素p对应的集合编号
    28             // O(h)复杂度,h为树的高度
    29             int find(int p){
    30                 assert( p >= 0 && p < count );
    31                 // 不断查询父节点,直到根节点
    32                 // 根节点特点:parent[p] = p
    33                 while( p != parent[p] )
    34                     p = parent[p];
    35                 return p; 
    36             }
    37             // 查看元素p和q是否属于一个集合
    38             // O(h)复杂度,h为树的高度
    39             int isConnected( int p , int q ){
    40                 return find(p) == find(q);
    41             }
    42             // 合并p和q所属的集合
    43             void unionElements(int p, int q){
    44                 int pRoot = find(p);
    45                 int qRoot = find(q);
    46                 if( pRoot == qRoot )
    47                     return;
    48                 if( sz[pRoot] < sz[qRoot] ){
    49                 parent[pRoot] = qRoot;
    50                 sz[qRoot] += sz[pRoot];
    51             }else{
    52                 parent[qRoot] = pRoot;
    53                 sz[pRoot] += sz[qRoot];
    54             }
    55         } 
    56     };
    57 }
    View Code

    UnionFind4.h

     1 #include<cassert>
     2 
     3 namespace UF4{
     4     
     5     class UnionFind{
     6         private:
     7             int* parent;  // parent[i]表示第i个元素所指向的父节点
     8             int* rank;  // rank[i]表示以i为根的集合中所表示树的层数(路径压缩中不维护其含义) 
     9             int count;  // 数据个数 
    10             
    11         public:
    12             UnionFind(int count){
    13                 parent = new int[count];
    14                 rank = new int[count]; 
    15                 this->count = count;
    16                 for( int i = 0 ; i < count ; i ++){ 
    17                     parent[i] = i;
    18                     rank[i] = 1;
    19                 } 
    20             }
    21             
    22             ~UnionFind(){
    23                 delete[] parent;
    24                 delete[] rank;
    25             } 
    26             
    27             // 查找元素p对应的集合编号
    28             // O(h)复杂度,h为树的高度
    29             int find(int p){
    30                 assert( p >= 0 && p < count );
    31                 // 不断查询父节点,直到根节点
    32                 // 根节点特点:parent[p] = p
    33                 while( p != parent[p] ){
    34                     // 路径压缩 
    35                     parent[p] = parent[parent[p]];
    36                     p = parent[p];
    37                 }
    38                 return p; 
    39 //                递归实现的路径压缩 
    40 //                if( p != parent[p] )
    41 //                    parent[p] = find(parent[p]);
    42 //                return parent[p];
    43             }
    44             // 查看元素p和q是否属于一个集合
    45             // O(h)复杂度,h为树的高度
    46             int isConnected( int p , int q ){
    47                 return find(p) == find(q);
    48             }
    49             // 合并p和q所属的集合
    50             void unionElements(int p, int q){
    51                 int pRoot = find(p);
    52                 int qRoot = find(q);
    53                 if( pRoot == qRoot )
    54                     return;
    55                 if( rank[pRoot] < rank[qRoot] ){
    56                     parent[pRoot] = qRoot;
    57                 }
    58                 else if( rank[qRoot] < rank[pRoot] )
    59                     parent[qRoot] = pRoot;
    60                 else{ // rank[qRoot] == rank[pRoot]
    61                     parent[pRoot] = qRoot;
    62                     rank[qRoot] += 1;
    63                 }
    64             }
    65     };
    66 }
    View Code

  • 相关阅读:
    Pytest中参数化之Excel文件实战
    PyCharm 专业版 2018激活(pycharm-professional-2018.2.4.exe)
    python语言编写一个接口测试的例子,涉及切割获取数据,读取表格数据,将结果写入表格中
    webdriver之UI界面下拉框的选择
    git的安装与详细应用
    安装jenkins
    python对数据库mysql的操作(增删改查)
    批量执行测试用例
    面向对象中多态的讲解+工厂设计模式的应用与讲解
    面向对象
  • 原文地址:https://www.cnblogs.com/cxc1357/p/12244377.html
Copyright © 2020-2023  润新知