并查集(Disjoint Set)

简介: 并查集(Disjoint Set)


基本用途

  • 处理不相交集合(disjoint sets)的合并和查询问题
  • 处理分组问题
  • 维护无序二元关系

基本操作

MakeSet(s):

建立一个新的并查集,其中包含s个集合,每个集合里只有一个元素。

UnionSet(x, y):

把元素×和元素y所在的集合合并。

要求×和y所在的集合不相交,如果相交则无需合并。

Find(x):

找到元素×所在的集合的代表。

该操作也可以用于判断两个元素是否位于同一个集合,只要将它们各自的代表比较一下就可以了。

内部实现

每个集合是一个树形结构

每个结点只需要保存一个值:它的父结点

最简单的实现是只用一个int数组fa, fa[x]表示编号为x的结点的父结点

根结点的fa等于它自己

初始化 MakeSet

合并 UnionSet

查询 Find + 路径压缩

路径压缩

并查集本质上只关心每个结点所在的集合,不关心该集合对应的树形结构具体是怎样的而一个结点所在的集合由根结点确定

因此在Find(x)的同时把x和×的所有祖先直接连到根结点上,下一次就可以一步走到根了

并查集还有一个优化叫做按秩合并(合并时把较浅的树合并到较深的上面)或者启发式合并(合并时把较小的树合并到较大的树上面)

同时采用路径压缩+按秩合并优化的并查集,单次操作的均摊复杂度为O(a(n))只采用其中一种,O(log(n))

α(n)是反阿克曼函数,是一个比 log(n)增长还要缓慢许多的函数,一般α(n)≤5,近似常数通常实现中为了简便,我们只使用路径压缩

代码实现

class DisjointSet
{
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;
    }
private:
    vector<int> fa;
};

进阶版本代码,加入集合数量记录

class UnionFind{
public:
    int find(int x){
        int root = x;
        while(father[root] != -1){
            root = father[root];
        }
        while(x != root){
            int original_father = father[x];
            father[x] = root;
            x = original_father;
        }
        return root;
    }
    bool is_connected(int x,int y){
        return find(x) == find(y);
    }
    void merge(int x,int y){
        int root_x = find(x);
        int root_y = find(y);
        if(root_x != root_y){
            father[root_x] = root_y;
            num_of_sets--;
        }
    }
    void add(int x){
        if(!father.count(x)){
            father[x] = -1;
            num_of_sets++;
        }
    }
    int get_num_of_sets(){
        return num_of_sets;
    }
private:
    // 记录父节点
    unordered_map<int,int> father;
    // 记录集合数量
    int num_of_sets = 0;
};

实战

547.省份数量

https://leetcode.cn/problems/number-of-provinces/

class UnionFind{
public:
    int find(int x){
        int root = x;
        while(father[root] != -1){
            root = father[root];
        }
        while(x != root){
            int original_father = father[x];
            father[x] = root;
            x = original_father;
        }
        return root;
    }
    bool is_connected(int x,int y){
        return find(x) == find(y);
    }
    void merge(int x,int y){
        int root_x = find(x);
        int root_y = find(y);
        if(root_x != root_y){
            father[root_x] = root_y;
            num_of_sets--;
        }
    }
    void add(int x){
        if(!father.count(x)){
            father[x] = -1;
            num_of_sets++;
        }
    }
    int get_num_of_sets(){
        return num_of_sets;
    }
private:
    // 记录父节点
    unordered_map<int,int> father;
    // 记录集合数量
    int num_of_sets = 0;
};
class Solution {
public:
    int findCircleNum(vector<vector<int>>& isConnected) {
        UnionFind uf;
        for(int i = 0;i < isConnected.size();i++){
            uf.add(i);
            for(int j = 0;j < i;j++){
                if(isConnected[i][j]){
                    uf.merge(i,j);
                }
            }
        }
        return uf.get_num_of_sets();
    }
};

130.被围绕的区域

https://leetcode.cn/problems/surrounded-regions/

class Solution {
public:
    void solve(vector<vector<char>>& board) {
        m = board.size();
        n = board[0].size();
        const int dx[4] = {-1, 0, 0, 1};
        const int dy[4] = {0, -1, 1, 0};
        fa = vector<int>(m * n + 1, 0);
        for(int i = 0; i <= m * n; i++) fa[i] = i;
        int outside = m*n;
        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 + dx[k];
                    int nj = j + dy[k];
                    if (ni < 0 || nj < 0 || ni >= m || nj >= n) {
                        unionSet(num(i, j), outside);
                    }else {
                        if (board[ni][nj] == 'O')
                            unionSet(num(i, j), num(ni, nj));
                    }
                }
            }
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++)
                if (board[i][j] == 'O' && find(num(i, j)) != find(outside))
                    board[i][j] = 'X';
    }
private:
    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;
    }
    int num(int i, int j) {
        return i * n + j;
    }
    int m, n;
    vector<int> fa;
};

145.超市

https://www.acwing.com/problem/content/147/

给定N个商品,每个商品有利润p;和过期时间d; ( 1 ≤N,p;, d≤ 10000)

每天只能卖一个商品,过期商品不能再卖

求如何安排每天卖的商品,可以使收益最大

并查集维护时间的占用情况,找到“deadline之前最近的空闲日”

#include "bits/stdc++.h"
using namespace std;
pair<int, int> a[10000];
int n;
int fa[10001];
int find(int x)
{
    if (x == fa[x])
        return x;
    return fa[x] = find(fa[x]);
}
int main()
{
    while (cin >> n)
    {
        for (int i = 0; i < n; i++)
            cin >> a[i].first >> a[i].second;
        sort(a, a + n);
        for (int i = 0; i <= 10000; i++)
            fa[i] = i;
        int ans = 0;
        for (int i = n - 1; i >= 0; i--)
        {
            int profit = a[i].first;
            int day = a[i].second;
            int lastAvailableDay = find(day);
            if (lastAvailableDay > 0)
            {
                ans += profit;
                fa[lastAvailableDay] = lastAvailableDay - 1;
            }
        }
        cout << ans << endl;
    }
}

推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

相关文章
|
5月前
|
算法 Java
并查集(Disjoint Set)
并查集(Disjoint Set)
39 1
|
6月前
|
算法 Python
Python高级数据结构——并查集(Disjoint Set)
Python高级数据结构——并查集(Disjoint Set)
285 2
|
算法 vr&ar
不相交集(The Disjoint Set ADT)
0)引论 不相交集是解决等价问题的一种有效的数据结构,之所以称之为有效是因为,这个数据结构简单(几行代码,一个简单数组就可以搞定),快速(每个操作基本上可以在常数平均时间内搞定)。 首先我们要明白什么叫做等价关系,而在这个之前要先有一个关系(relation)的定义 Relation:定义在数据集S上的关系R是指,对于属于数据集S中的每一对元素(a,b),a R b要么是真要么是假。
1085 0
|
1月前
|
存储 JavaScript 前端开发
Set、Map、WeakSet 和 WeakMap 的区别
在 JavaScript 中,Set 和 Map 用于存储唯一值和键值对,支持多种操作方法,如添加、删除和检查元素。WeakSet 和 WeakMap 则存储弱引用的对象,有助于防止内存泄漏,适合特定场景使用。
|
2月前
|
存储 Java API
【数据结构】map&set详解
本文详细介绍了Java集合框架中的Set系列和Map系列集合。Set系列包括HashSet(哈希表实现,无序且元素唯一)、LinkedHashSet(保持插入顺序的HashSet)、TreeSet(红黑树实现,自动排序)。Map系列为双列集合,键值一一对应,键不可重复,值可重复。文章还介绍了HashMap、LinkedHashMap、TreeMap的具体实现与应用场景,并提供了面试题示例,如随机链表复制、宝石与石头、前K个高频单词等问题的解决方案。
37 6
【数据结构】map&set详解
|
1月前
|
存储 缓存 Java
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
36 1
|
2月前
|
算法
你对Collection中Set、List、Map理解?
你对Collection中Set、List、Map理解?
36 5
|
2月前
|
存储 JavaScript 前端开发
js的map和set |21
js的map和set |21
|
2月前
|
存储 前端开发 API
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用
该文章详细介绍了ES6中Set和Map数据结构的特性和使用方法,并探讨了它们在前端开发中的具体应用,包括如何利用这些数据结构来解决常见的编程问题。
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用