并查集(Disjoin Set)
在现实生活中,我们会遇到许多表示不同事务/对象之间的关系都可以转化为图的问题。所有关于图的操作基本都可以转化为图的连通性问题,下面将介绍一个常用于判断图的连通性的数据结构——并查集(UnionFind)。
1. 基本概念
-
定义
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
注:
在UnionFind中,一棵树表示一个联通集合
-
特征
a. 相互联通的2个节点在一个集合中;
b. 在集合中,相邻的两个点用一条边表示,形成一棵树;
c. 图的不同的连通分量构成UnionFind中不同的连通集合,形成森林;
2. 数据结构和表示
-
数据结构
在程序中,UnionFind用以下两种形式表示:
a. 数组
b. key-value价值对
-
表示方法
a. 根据特征1,在UnionFind的一个联通的集合中,任意两个节点都可以相互到达,所以只需要记录节点之间的两两关系就可以通过一个节点找到另一个节点;
b. 根据特征2,由于一个联通集合就是一棵树,所以在表示节点关系的时候,只需要记录节点的父节点;
c. 根据特征2和3:
- 由于一个连通集合就是一棵树,通过根节点可以到达任何一个子节点,所以用 “根节点” 作为代表元表示UnionFind中的一个连通集合;
- 由于一个UnionFind就是一个森林,所以,所以,根节点的数量就是UnionFind中连通集合的数量;
-
表示
a. 数组表示
下标:表示UnionFind中的一个节点;
元素值:表示下标代表节点的父节点
b. Map表示
Key: 表示UnionFind中的一个节点;
Value: 表示key代表节点的父节点
注:根节点存储的父节点是它本身。
3. 操作
UnionFind主要提供两种快速的操作:查找和合并,这也是并查集名字的由来。
-
查找 Find
通过查找操作,能够统统一个元素快速找到它所在连通集合的根节点。
-
合并 Union
通过合并操作,将并查集中两个不相互联通的集合合并为一个连通的集合;
4. Code
#include <vector>
using namespace std;
class UnionFind{
private:
vector<int> father;
int count;
public:
UnionFind1(int count){
father = vector<int>(count);
for(int i = 0; i < count; i++){
father[i] = i;
}
this->count = count;
}
int find_union(int x){
return father[x] == x? father[x] : find_union(father[x]);
}
void merge_union(int a, int b){
int fatherA = find_union(a);
int fatherB = find_union(b);
if(fatherA != fatherB){
father[fatherA] = fatherB;
}
}
};
5. 路径压缩
-
背景
当从根节点到叶子节点形成一条链时,每次从一个节点查找根节点时必须从头开始遍历,直到找到根节点,这个过程会访问许多中间无用的节点。
-
原理
为了降低递归查找根节点的时间复杂度,在查找根节点的过程中,让节点直接连接到根节点即节点直接记录根节点的值,则经过路径压缩后,在O(1)的时间复杂度内就可以获得根节点。
-
Code
路径压缩需要在查找的过程中将节点的父节点修改为根节点,因此,只有find函数不同,下面只提供find函数代码。
int find_union(int x){ if(father[x] != x){ father[x] = find_union(father[x]); } return father[x] == x? father[x] : find_union(father[x]); }
6. 应用
下面给出UnionFind的常用应用场景,在遇到实际问题时,只需将问题转化为图的问题即可:
- 图中是否有环
- 图中的两个节点是否相互可达
- 图的连通分量计算
7. 题目
- Leetcode 399 Evaluate Division
- Leetcode 839 Similar String Groups (注:UnionFind表示方法选择)
由于小编个人能力的原因,文章中如出现错误或描述不清,欢迎大家指出!