特点
常用于确定无向图的连通分量(虽然DFS也可以做),求最小公共祖先(LCA)
朴素版并查集
vector<int> v;
void Union(int i, int j){
int pi = Find(i);
int pj = Find(j);
v[pi] = pj;
}
int Find(int i){
while(v[i] != i) i = v[i];
return i;
}
路径压缩
为了减少树的高度,直接将查找路径上的结点连接到根上去,降低查找时间
int Find(int i){
if(v[i] != i) v[i] = Find(v[i]);
return v[i];
}
按秩合并
秩表示结点高度的上界,(算法导论上说路径压缩不会改变每个结点的秩?没想通,难道是指的上界),将秩小的树指向秩大的树称为按秩合并
void Union(int i, int j){
int ri = Find(i);
int rj = Find(j);
if(ri == rj) return ;
if(Rank[ri] > Rank[rj]){
v[rj] = ri;
} else if(Rank[ri] < Rank[rj]){
v[ri] = rj;
} else {
Rank[ri] += 1;
v[rj] = ri;
}
}
时间复杂度
朴素版并查集的Union操作的摊还时间为(O(n))
使用按秩合并的Union操作的摊还时间为(O(log_2 n))
同时采用按秩合并和路径压缩的最坏运行时间每个操作的摊还时间为(alpha(n))(反阿克曼函数,近似为一个常数)
详细分析可以写一本书...
例题
class Solution {
private:
vector<int> Rank;
vector<int> v;
void Union(int i, int j){
int ri = Find(i);
int rj = Find(j);
if(ri == rj) return ;
if(Rank[ri] > Rank[rj]){
v[rj] = ri;
} else if(Rank[ri] < Rank[rj]){
v[ri] = rj;
} else {
Rank[ri] += 1;
v[rj] = ri;
}
}
int Find(int i){
if(v[i] != i) v[i] = Find(v[i]);
return v[i];
}
public:
int findCircleNum(vector<vector<int>>& M) {
int n = M.size();
for(int i = 0; i < n; i++){
v.push_back(i);
Rank.push_back(0);
}
for(int i = 0; i < n; i++){
for(int j = i + 1; j < n; j++){
if(M[i][j] == 1){
Union(i, j);
}
}
}
int cnt = 0;
for(int i = 0; i < n; i++){
if(Find(i) == i) cnt++;
}
return cnt;
}
};