并查集基本原理
并查集
1. 概念
并查集主要是为了解决图论中的动态连通性的问题。现在主要的Union-Find算法主要实现两个API:
class UF{
public:
void union(int p, int q);
bool connected(int p, int q);
int count();
}
这里所说的「连通」是一种等价关系,也就是说具有如下三个性质:
1、自反性:节点 p
和 p
是连通的。
2、对称性:如果节点 p
和 q
连通,那么 q
和 p
也连通。
3、传递性:如果节点 p
和 q
连通,q
和 r
连通,那么 p
和 r
也连通。
2.基本思路
首先就是初始化,并查集需要用到一个数组。
class UF{
private:
//用来记录连通分量
int count;
//用来记录每个节点的父节点
vector<int> parent;
//用于平衡性优化,避免头重脚轻
vector<int> sizes;
public:
UF(int n) {
this->count = n;
parent.resize(n);
//sizes.resize(n);
for (int i = 0; i < n; i++) {
parent[i] = i;
//sizes[i] = 1;
}
}
//连通节点p和节点q
void union(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP == rootQ) return;
parent[rootQ] = rootP;
count--;
}
//判断两个节点是否联通
bool connected(int p, int q) {
int rootp = find(p);
int rootq = find(q);
return rootp == rootq;
}
//查找x节点的父节点,并进行路径压缩
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
//获取连通分量
int getCount() {
return this->count;
}
}
Union-Find 算法的复杂度可以这样分析:构造函数初始化数据结构需要 O(N) 的时间和空间复杂度;连通两个节点 union
、判断两个节点的连通性 connected
、计算连通分量 count
所需的时间复杂度均为 O(1)。
到这里,相信你已经掌握了 Union-Find 算法的核心逻辑,总结一下我们优化算法的过程:
1、用 parent
数组记录每个节点的父节点,相当于指向父节点的指针,所以 parent
数组内实际存储着一个森林(若干棵多叉树)。
2、用 size
数组记录着每棵树的重量,目的是让 union
后树依然拥有平衡性,保证各个 API 时间复杂度为 O(logN),而不会退化成链表影响操作效率。
3、在 find
函数中进行路径压缩,保证任意树的高度保持在常数,使得各个 API 时间复杂度为 O(1)。使用了路径压缩之后,可以不使用 size
数组的平衡优化。