【并查集】【Union-Find】

文章介绍了并查集这种数据结构的基本概念,包括合并、查找和初始化操作,并提供了两个不同的模板实现,基于LeetCode547题目的解决方案。此外,还讨论了使用并查集解决LeetCode130题的两种方法,分别是深度优先搜索(DFS)和并查集策略,强调了并查集在处理连通性问题时的效率。
摘要由CSDN通过智能技术生成

基本概念

  • 并查集是一种数据结构
  • 并查集这三个字,一个字代表一个意思。
    并(Union),代表合并
    查(Find),代表查找
    集(Set),代表这是一个以字典为基础的数据结构,它的基本功能是合并集合中的元素,查找集合中的元素
  • 并查集的典型应用是有关连通分量的问题
  • 并查集解决单个问题(添加,合并,查找)的时间复杂度都是O(1)
  • 因此,并查集可以应用到在线算法中

链接:https://leetcode.cn/problems/number-of-provinces/solution/python-duo-tu-xiang-jie-bing-cha-ji-by-m-vjdr/

并查集模板(LeetCode547为例)

1.模板一

https://leetcode.cn/problems/number-of-provinces/solution/python-duo-tu-xiang-jie-bing-cha-ji-by-m-vjdr/

class UnionFind{
public:
    // 查找祖先:如果节点的父节点不为空,那就不断迭代。
    int find(int x){
        int root = x;
        
        while(father[root] != -1){
            root = father[root];
        }
        // 压缩路径:也就是把树的深度固定为二,x 到根节点之间的所有节点直接接到根节点下⾯
        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 Union(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;
};

2.模板二

/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2021. All rights reserved.
 * Description: 上机编程认证
 * Note: 缺省代码仅供参考,可自行决定使用、修改或删除
 */
#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>

using namespace std;
class UnionFind {
public:
    /* n为图中节点的个数 */
    UnionFind(int n)
    {
        this->nums_of_sets = n; // ⼀开始互不连通, this可省略
        cout << "numss:" << this->nums_of_sets << '\n';
        for (int i = 0; i < nums_of_sets; i++) {
            this->parent[i] = i; // ⽗节点指针初始指向⾃⼰
        }
    }

    /* 合并两个节点,如果发现两个节点是连通的,那么就要把他们合并,也就是他们的祖先是相同的。这里究竟把谁当做父节点一般是没有区别的 */
    void Union(int x, int y)
    {
        int root_x = Find(x);
        int root_y = Find(y);
        if (root_x == root_y) { // 根节点相同不合并
            return;
        }

        parent[root_x] = root_y; // 将两棵树合并为⼀棵, parent[rootQ] = rootP 也⼀样
        nums_of_sets--; // 两个分量合⼆为⼀
        cout << "nums:" << nums_of_sets << endl;
    }

    /* 查找祖先:如果节点的父节点不为空,那就不断迭代。*/
    int Find(int x)
    {
        // 根节点的 parent[x] == x
        while (parent[x] != x) {
            x = parent[x];
        }
        return x;
    }

    /* 返回当前的联通分量个数 */
    int Get_num_of_sets()
    {
        return nums_of_sets;
    }

private:
    int nums_of_sets; // 记录联通分量个数
    unordered_map<int, int> parent; // 记录每个节点的父节点
};

class Solution {
public:
    int findCircleNum(vector<vector<int>>& isConnected) {
        UnionFind uf(isConnected.size());
        for(int i = 0; i < isConnected.size(); i++){
            for(int j = 0; j < i; j++){
                if(isConnected[i][j]){
                    uf.Union(i, j);
                }
            }
        }

        return uf.Get_num_of_sets();
    }
};
// 以下为考题输入输出框架,此部分代码不建议改动
using namespace std;
int main()
{
    int x = 0;
    vector<vector<int>> vec;
    vector<int> v;
    while(cin >> x) {
        v.push_back(x);
        if(cin.get() == '\n'){ // 从流中提取的字符为换行符的话
            vec.push_back(v);
            v.clear();
        }

        if(cin.peek() == '\n'){ //观测到当前字符为换行符的话, 连续输入两个换行结束
            break;
        }
    }
    cout << "row:" << vec.size() << endl;
    cout << "col:" << vec[0].size() << endl;
    Solution S;
    int ret = S.findCircleNum(vec);
    cout << "ret is " << ret << endl;
    return 0;
}

LeetCode130 被围绕的区域

1. 解法一:DFS

class Solution {
public:
    
    void dfs (vector<vector<char>>& board,int x ,int y) //dfs将没有被围绕的O作标记
    {
        if (x < 0 || x >= n || y < 0 || y >= m || board[x][y] != 'O' )
            return ;

        board[x][y] = 'M';

        dfs (board, x + 1, y);
        dfs (board, x - 1, y);
        dfs (board, x , y + 1);
        dfs (board, x, y - 1);
    }

    void solve(vector<vector<char> >& board) {
        n = board.size(); //行
        if (n == 0) return ;

        m = board[0].size(); //列,此语句不能放在if(n==0)前面
        for (int i = 0 ; i < n; ++i) { //标记第一列和最后一列的‘O’
            dfs (board,i,0); //第一列
            dfs (board,i,m - 1); //最后一列
        }

        for (int j = 1 ; j < m -1 ; ++j) { //标记第一行和最后一行的‘O’
            dfs (board,0,j); //第一行
            dfs (board,n - 1,j); //最后一行
        }

        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                if (board[i][j] == 'M')
                    board[i][j] = 'O';
                else 
                    board[i][j] = 'X';
            }
        }
    }
private:
    int n, m; //全局变量
};

2.解法二:并查集

/// 方法二:并查集 必须是四⾯被围的 O 才能被换成 X,也就是说边⻆上的 O ⼀定不会被围,进⼀步,与边⻆上的 O 相
// 连的 O 也不会被 X 围四⾯,也不会被替换
class UnionFind {
public:
    /* n为图中节点的个数 */
    UnionFind(int n)
    {
        this->nums_of_sets = n; // ⼀开始互不连通, this可省略
        cout << "numss:" << this->nums_of_sets << '\n';
        for (int i = 0; i < nums_of_sets; i++) {
            this->parent[i] = i; // ⽗节点指针初始指向⾃⼰
        }
    }

    /* 合并两个节点,如果发现两个节点是连通的,那么就要把他们合并,也就是他们的祖先是相同的。这里究竟把谁当做父节点一般是没有区别的 */
    void Union(int x, int y)
    {
        int root_x = Find(x);
        int root_y = Find(y);
        if (root_x == root_y) { // 根节点相同不合并
            return;
        }

        parent[root_x] = root_y; // 将两棵树合并为⼀棵, parent[rootQ] = rootP 也⼀样
        nums_of_sets--; // 两个分量合⼆为⼀
        cout << "nums:" << nums_of_sets << endl;
    }

    /* 查找祖先:如果节点的父节点不为空,那就不断迭代。*/
    int Find(int x)
    {
        // 根节点的 parent[x] == x,先找到根节点
        int root = x;
        while (parent[root] != root) {
            root = parent[root];
        }
        
        // 然后把 x 到根节点之间的所有节点直接接到根节点下⾯
        while (x != root) {
            int origin_father = parent[x];
            parent[x] = root;
            x = origin_father; 
        }
        return root;
    }

    /* 返回当前的联通分量个数 */
    int Get_num_of_sets()
    {
        return nums_of_sets;
    }

    /* 判断节点 p 和节点 q 是否连通 */
    bool is_connected(int x,int y){
        return Find(x) == Find(y);
    }
private:
    int nums_of_sets; // 记录联通分量个数
    unordered_map<int, int> parent; // 记录每个节点的父节点
};




class Solution {
public:
    void solve(vector<vector<char>>& board) {
        int m = board.size();
        int n = board[0].size();
        UnionFind uf(m * n + 1);
        int dummy = m * n;
        
        // 将首列和末列的 O 与 dummy 连通
        for (int i = 0; i < m; i++) {
            if (board[i][0] == 'O') {
                uf.Union(dummy, i * n);
            }
            if (board[i][n - 1] == 'O') {
                uf.Union(dummy, i * n + n - 1);
            }
        }

        // 将首行和末行的 O 与 dummy 连通
        for (int j = 0; j < n; j++) {
            if (board[0][j] == 'O') {
                uf.Union(dummy, j);
            }
            if (board[m - 1][j] == 'O') {
                uf.Union(dummy, (m - 1) * n + j);
            }
        }
        // 方向数组 d 是上下左右搜索的常用手法
        vector<vector<int> > direction{{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
        for (int i = 1; i < m - 1; i++) {
            for (int j = 1; j < n - 1; j++) {
                if (board[i][j] == 'O') {
                    // 将此O与上下左右的O进行联通
                    for (int k = 0; k < direction.size(); k++) {
                        int x = i + direction[i][j];
                        int y = j + direction[i][j];
                        if (board[x][y] == 'O') {
                            uf.Union(x * n + y, i * n + j);
                        }
                    }
                }
            }
        }
        // 所有不和 dummy 连通的 O,都要被替换
        for (int i = 1; i < m - 1; i++) {
            for (int j = 1; j < n - 1; j++) {
                if (!uf.is_connected(dummy, i * n + j))
                    board[i][j] = 'X';
            }
        }
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值