引入
- 连接问题:一个结点和另一个结点是否相连
- 网络中节点间的连接状态
- 微信中任意两个人是否通过好友认识
- 路径问题:一个结点到另一个结点的路径
- 集合问题:经常使用并集,需要查询元素是否是一类
操作
- 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 }
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 }
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 }
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 }
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 }