并查集(Disjoint Set Union,DSU) 是一种用于管理元素分组的数据结构,主要支持两种操作:
查找(Find) :确定某个元素属于哪个集合。
合并(Union) :将两个集合合并为一个集合。
并查集广泛应用于解决动态连通性问题,例如判断图中的两个节点是否连通、统计连通分量等。
1. 核心思想 并查集的核心思想是用一个父节点数组 来表示每个元素的所属集合。初始时,每个元素都是一个独立的集合,父节点指向自己。通过路径压缩和按秩合并等优化技术,可以高效地支持查找和合并操作。
2. 基本操作 (1)初始化
每个元素初始时都是一个独立的集合,父节点指向自己。
例如,有 5 个元素:[0, 1, 2, 3, 4]
,初始化后父节点数组为 parent = [0, 1, 2, 3, 4]
。
(2)查找(Find)
查找某个元素所属的集合(即根节点)。
通过递归或迭代的方式,沿着父节点向上查找,直到找到根节点。
路径压缩优化 :在查找过程中,将路径上的所有节点直接指向根节点,以加快后续查找速度。
(3)合并(Union)
将两个元素所在的集合合并为一个集合。
先找到两个元素的根节点,然后将其中一个根节点的父节点指向另一个根节点。
按秩合并优化 :在合并时,将较小的树合并到较大的树上,以保持树的平衡。
3. 并查集的实现 以下是并查集的 Python 实现,包含路径压缩和按秩合并优化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class UnionFind : def __init__ (self, n ): self .parent = list (range (n)) self .rank = [1 ] * n def find (self, x ): if self .parent[x] != x: self .parent[x] = self .find(self .parent[x]) return self .parent[x] def union (self, x, y ): root_x = self .find(x) root_y = self .find(y) if root_x != root_y: if self .rank[root_x] > self .rank[root_y]: self .parent[root_y] = root_x elif self .rank[root_x] < self .rank[root_y]: self .parent[root_x] = root_y else : self .parent[root_y] = root_x self .rank[root_x] += 1
4. 并查集的应用场景
动态连通性问题 :
最小生成树算法(Kruskal 算法) :
朋友圈问题 :
图像处理 :
5. 并查集的时间复杂度
初始化 :O(n),其中 n 是元素个数。
查找(Find) :接近 O(1),经过路径压缩后,查找操作的时间复杂度接近常数。
合并(Union) :接近 O(1),经过按秩合并优化后,合并操作的时间复杂度接近常数。
6. 并查集的优化
路径压缩 :
在查找过程中,将路径上的所有节点直接指向根节点,以加快后续查找速度。
按秩合并 :
在合并时,将较小的树合并到较大的树上,以保持树的平衡,避免树过高。
7. 并查集的经典问题 (1)判断图中是否有环
遍历图中的每条边,如果两个端点属于同一集合,则说明存在环。
(2)统计连通分量
(3)朋友圈问题
给定一组朋友关系,判断有多少个朋友圈(连通分量)。
8. 代码示例:朋友圈问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class UnionFind : def __init__ (self, n ): self .parent = list (range (n)) self .rank = [1 ] * n def find (self, x ): if self .parent[x] != x: self .parent[x] = self .find(self .parent[x]) return self .parent[x] def union (self, x, y ): root_x = self .find(x) root_y = self .find(y) if root_x != root_y: if self .rank[root_x] > self .rank[root_y]: self .parent[root_y] = root_x elif self .rank[root_x] < self .rank[root_y]: self .parent[root_x] = root_y else : self .parent[root_y] = root_x self .rank[root_x] += 1 def find_circle_num (M ): n = len (M) uf = UnionFind(n) for i in range (n): for j in range (i + 1 , n): if M[i][j] == 1 : uf.union(i, j) return len (set (uf.find(i) for i in range (n)) M = [ [1 , 1 , 0 ], [1 , 1 , 0 ], [0 , 0 , 1 ] ] print (find_circle_num(M))
总结 并查集是一种高效管理分组的数据结构,核心操作是查找和合并。通过路径压缩和按秩合并优化,可以显著提高性能。它在动态连通性问题、最小生成树算法、朋友圈问题等场景中有广泛应用。
以下是使用 C++ 实现的并查集代码,包含路径压缩和按秩合并优化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 #include <iostream> #include <vector> using namespace std;class UnionFind {private : vector<int > parent; vector<int > rank; public : UnionFind (int n) { parent.resize (n); rank.resize (n, 1 ); for (int i = 0 ; i < n; i++) { parent[i] = i; } } int find (int x) { if (parent[x] != x) { parent[x] = find (parent[x]); } return parent[x]; } void unionSets (int x, int y) { int rootX = find (x); int rootY = find (y); if (rootX != rootY) { if (rank[rootX] > rank[rootY]) { parent[rootY] = rootX; } else if (rank[rootX] < rank[rootY]) { parent[rootX] = rootY; } else { parent[rootY] = rootX; rank[rootX]++; } } } bool isConnected (int x, int y) { return find (x) == find (y); } }; int main () { int n = 5 ; UnionFind uf (n) ; uf.unionSets (0 , 1 ); uf.unionSets (2 , 3 ); uf.unionSets (1 , 4 ); cout << "0 and 4 are connected: " << uf.isConnected (0 , 4 ) << endl; cout << "2 and 4 are connected: " << uf.isConnected (2 , 4 ) << endl; return 0 ; }
代码说明
初始化 :
parent
数组存储每个元素的父节点,初始时每个元素的父节点是自己。
rank
数组存储每个集合的秩(树的高度),初始时为 1。
查找操作(find
) :
通过递归查找根节点,并在查找过程中进行路径压缩,将路径上的所有节点直接指向根节点。
合并操作(unionSets
) :
找到两个元素的根节点,按秩合并(将较小的树合并到较大的树上)。
判断连通性(isConnected
) :
示例运行 输入:
1 2 3 4 5 int n = 5 ;UnionFind uf (n) ;uf.unionSets (0 , 1 ); uf.unionSets (2 , 3 ); uf.unionSets (1 , 4 );
输出:
1 2 0 and 4 are connected: 1 2 and 4 are connected: 0
并查集的应用
动态连通性问题 :
最小生成树算法(Kruskal 算法) :
朋友圈问题 :
总结 并查集是一种高效管理分组的数据结构,核心操作是查找和合并。通过路径压缩和按秩合并优化,可以显著提高性能。以上代码是 C++ 实现的标准模板,适用于大多数并查集相关的问题。