用途
处理不相加集合的合并和查询问题—》处理分组问题—》维护无序二元关系
基本操作
MakeSet(s):
建立一个新的并查集,其中包含s个集合,每个集合里只有一个元素。
UnionSet(x, y):
把元素x和元素y所在的集合合并。
要求x和y所在的集合不相交,如果相交则无需合并。
Find(x):
找到元素x所在的集合的代表。
该操作也可以用于判断两个元素是否位于同一个集合,只要将它们各自的代表比较一下就可以了。
内部实现
每个集合是一个树形结构
每个节点只需要保存一个值:它的父节点
最简单的实现是只用一个int数组fa, fa[x]表示编号为x的节点的父节点
根节点的fa等于它自己
class DisjointSet{
private:
vector<int> fa;
public:
//一个int数组fa, fa[x]表示编号为x的节点的父节点根节点的fa等于它自己
DisjointSet(int n){
fa = vector<int>(n, 0);
for(int i=0; i<n; ++i) fa[i] = i;
}
//查找节点的根节点,查找的同时进行路径压缩,即将查找路径上的节点的父节点都标记为根节点
int find(int x){
if(x==fa[x]) return x;
return fa[x] = find(fa[x]);
}
//找到该节点所在树形集合的根节点,如果根节点相同,说明已经合并,不相同则将其中一个节点的根节点的父节点设为另一个节点的根节点
void unionSet(int x, int y){
x = find(x), y = find(y);
if(x != y) fa[x] = y;
}
};
JAVA版
import java.util.Set;
import java.util.HashSet;
import java.util.Arrays;
public class UnionFindSet {
// 并查集
public final int n;
public int[] fa, rank, num;
public UnionFindSet(final int n) {
this.n = n;
this.fa = new int[n];
this.rank = new int[n];
this.num = new int[n];
for (int i = 0; i < n; i++) fa[i] = i;
Arrays.fill(rank, 1);
Arrays.fill(num, 1);
}
public UnionFindSet(final int n, final int initial) {
this.n = n;
this.fa = new int[n];
this.rank = new int[n];
this.num = new int[n];
for (int i = 0; i < n; i++) fa[i] = i + initial;
Arrays.fill(rank, 1);
Arrays.fill(num, 1);
}
public int find(int x) { // 查找元素x所在集合的代表元素
if (x == fa[x]) return x;
fa[x] = find(fa[x]);
return fa[x];
}
public void union(int i, int j) { // 合并元素i和j所在的集合
int x = find(i), y = find(j);
if (rank[x] <= rank[y]) fa[x] = y;
else fa[y] = x;
if (rank[x] == rank[y] && x != y) rank[y]++;
if (x != y) num[x] = num[y] = num[x] + num[y];
}
public boolean check(int i, int j) { // 判断元素i和j是否属于同一集合
return find(i) == find(j);
}
public int number(int x) { // 查询元素x所在集合的元素个数
return num[find(x)];
}
public int total() { // 查询不同集合的总数
Set<Integer> set = new HashSet<>();
for (int i = 0; i < n; i++) {
fa[i] = find(i);
set.add(fa[i]);
}
return set.size();
}
}
时间复杂度
同时采用路径压缩+按秩合并,单次操作的均摊复杂度为O(a(n)<=5),只采用其中一种,O(logn)。
练习题
class DisjointSet{
public:
vector<int> fa;
public:
DisjointSet(int n){
fa = vector<int>(n, 0);
for(int i=0; i<n; ++i) fa[i] = i;
}
int find(int x){
if(x==fa[x]) return x;
return fa[x] = find(fa[x]);
}
void unionSet(int x, int y){
x = find(x), y = find(y);
if(x != y) fa[x] = y;
}
};
class Solution {
public:
int findCircleNum(vector<vector<int>>& isConnected) {
int n = isConnected.size();
DisjointSet* dj = new DisjointSet(n);
for(int i=0; i<n; ++i){
for(int j=0; j<n; ++j){
if(isConnected[i][j]==1){
dj->unionSet(i, j);
}
}
}
int res = 0;
for(int i=0; i<n; ++i){
if(i == dj->fa[i]) res++;
}
return res;
}
};
class DisjointSet{
public:
vector<int> fa;
public:
DisjointSet(int n){
fa = vector<int>(n, 0);
for(int i=0; i<n; ++i) fa[i] = i;
}
int find(int x){
if(x==fa[x]) return x;
return fa[x] = find(fa[x]);
}
void unionSet(int x, int y){
x = find(x), y = find(y);
if(x != y) fa[x] = y;
}
};
class Solution {
private:
int m, n;
const int di[4] = {-1, 0, 0, 1};
const int dj[4] = {0, -1, 1, 0};
inline int num(int i, int j){
return i*n+j;
}
public:
void solve(vector<vector<char>>& board) {
m = board.size();
n = board[0].size();
DisjointSet* djs = new DisjointSet(m*n+1);
for(int i=0; i<m; ++i){
for(int j=0; j<n; ++j){
if(board[i][j]=='X') continue;
for(int k=0; k<4; ++k){
int ni = i+di[k];
int nj = j+dj[k];
if(ni<0 || nj<0 || ni>=m ||nj>=n){
djs->fa[djs->find(num(i, j))] = djs->find(n*m);
}else if(board[ni][nj] == 'O'){
djs->unionSet(num(ni, nj),num(i,j));
}
}
}
}
for(int i=0; i<m; ++i){
for(int j=0; j<n; ++j){
if(board[i][j]=='O' && djs->find(num(i, j))!=djs->find(n*m)){
board[i][j]='X';
}
}
}
}
};