并查集及相关练习题

用途

处理不相加集合的合并和查询问题—》处理分组问题—》维护无序二元关系

基本操作

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)。

练习题

leetcode547 省份数量

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;
    }
};

leetcode130 被围绕的区域

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';
                }
            }
        }
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值