• [每日算法] 并查集数据结构及其实例-- day15


    ✨博主介绍

    💂 个人主页:苏州程序大白

    💂 个人社区:CSDN全国各地程序猿

    🤟作者介绍:中国DBA联盟(ACDU)成员,CSDN全国各地程序猿(媛)聚集地管理员。目前从事工业自动化软件开发工作。擅长C#、Java、机器视觉、底层算法等语言。2019年成立柒月软件工作室,2021年注册苏州凯捷智能科技有限公司

    💅 有任何问题欢迎私信,看到会及时回复

    👤 微信号:stbsl6,微信公众号:苏州程序大白

    💬如果文章对你有帮助,欢迎关注、点赞、收藏(一键三连)

    🎯 想加入技术交流群的可以加我好友,群里会分享学习资料

    并查集

    在这里插入图片描述
    并查集是简洁而优雅的数据结构之一,主要用于解决一些元素分组的问题。它管理一系列不相交的集合,并支持两种操作:

    • 合并(Union):把两个不相交的集合合并为一个集合。

    • 查询(Find):查询两个元素是否在同一个集合中。

    数据结构

    并查集的重要思想在于:用集合中的一个元素代表集合

    如果把集合比喻成帮派,而代表元素则是帮主,接下来我们利用这个比喻,看看并查集是如何运作的。
    在这里插入图片描述
    最开始,所有大侠各自为战。他们各自的帮主自然就是自己。(对于只有一个元素的集合,代表元素自然是唯一的那个元素)
    在这里插入图片描述
    现在2号想和3号比武(合并3号和2号所在的集合),但3号表示,别跟我打,让我帮主来收拾你(合并代表元素)。不妨设这次又是1号赢了,那么2号也认1号做帮主。

    在这里插入图片描述
    现在我们假设4、5、6号也进行了一番帮派合并,江湖局势变成下面这样:
    在这里插入图片描述
    现在假设2号想与6号比,跟刚刚说的一样,喊帮主1号和4号出来打一架(帮主真辛苦啊)。1号胜利后,4号认1号为帮主,当然他的手下也都是跟着投降了。
    在这里插入图片描述
    综上所述,并查集是一个树状的结构,要寻找集合的代表元素,只需要一层一层往上访问父节点(图中箭头所指的圆),直达树的**根节点(图中橙色的圆)**即可。根节点的父节点是它自己。我们可以直接把它画成一棵树:
    在这里插入图片描述

    数据结构核心代码

    Init

    假如有编号为1, 2, 3, …, n的n个元素,我们用一个数组fa[]来存储每个元素的父节点(因为每个元素有且只有一个父节点,所以这是可行的)。一开始,我们先将它们的父节点设为自己。

    int fa[MAXN];
    void init(int n)
    {
        for (int i = 1; i <= n; ++i)
            fa[i] = i;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Find

    我们用递归的写法实现对代表元素的查询:一层一层访问父节点,直至根节点(根节点的标志就是父节点是本身)。要判断两个元素是否属于同一个集合,只需要看它们的根节点是否相同即可。

    注意:该查询算法有优化方案,见后文。

    int find(int x)
    {
        if(fa[x] == x)
            return x;
        else
            return find(fa[x]);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Union

    合并操作也是很简单的,先找到两个集合的代表元素,然后将前者的父节点设为后者即可。比如如下代码是i帮派加入j的帮派。

    void merge(int i, int j)
    {
        fa[find(i)] = find(j);
    }
    
    • 1
    • 2
    • 3
    • 4

    Find(Optimized)

    既然我们只关心一个元素对应的根节点,那我们希望每个元素到根节点的路径尽可能短,最好只需要一步。我们可以优化合并,防止形成过长的路径。

    路径压缩:只要我们在查询的过程中,把沿途的每个节点的父节点都设为根节点即可。

    对比:
    在这里插入图片描述在这里插入图片描述

    int find(int x)
    {
        return x == fa[x] ? x : (fa[x] = find(fa[x]));
    }
    
    • 1
    • 2
    • 3
    • 4

    为了好看, 以上代码等价为:

    int find(int x)
    {
        if(x == fa[x])
            return x;
        else{
            fa[x] = find(fa[x]);  //父节点设为根节点
            return fa[x];         //返回父节点
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    例题

    剑指 Offer II 118. 多余的边

    树可以看成是一个连通且 无环 的 无向 图。
    
    给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边。
    
    请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的边。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    输入: edges = [[1,2],[1,3],[2,3]]
    输出: [2,3]
    
    • 1
    • 2

    题解:

    在一棵树中,边的数量比节点的数量少 1。如果一棵树有 n 个节点,则这棵树有 n-1 条边。这道题中的图在树的基础上多了一条边,因此边的数量也是 n。

    树是一个连通且无环的无向图,在树中多了一条边之后就会出现环,因此多余的边即为导致环出现的边。

    可以通过并查集寻找多余的边。初始时,每个节点都属于不同的连通分量。遍历每一条边,判断这条边连接的两个顶点是否属于相同的连通分量。

    如果两个顶点属于不同的连通分量,则说明在遍历到当前的边之前,这两个顶点之间不连通,因此当前的边不会导致环出现,合并这两个顶点的连通分量。

    如果两个顶点属于相同的连通分量,则说明在遍历到当前的边之前,这两个顶点之间已经连通,因此当前的边导致环出现,为多余的边,将当前的边作为答案返回。

    class Solution {
        int[] p;
        //查询根节点爸爸
        int find(int x){
            return p[x]==x ? x : (p[x]=find(p[x]));
        }
        //连接两个节点, 认b的爸爸为新爸爸
        void union(int a,int b){
            p[find(a)] = find(b);
        }
        //判断两个节点是否连通,是否是同一个爸爸即可
        boolean query(int a,int b){
            return find(a) == find(b);
        }
        public int[] findRedundantConnection(int[][] edges) {
            int n = edges.length;
            p = new int[n+1];
            //初始化并查集,先自己认自己为爸爸
            for(int i=0;i<=n;i++)    p[i] = i;
            for(int[] e : edges){
                if(query(e[0], e[1])) return e;
                else union(e[0], e[1]);
            }
            return null;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    💫点击直接资料领取💫

    在这里插入图片描述

    ❤️关注苏州程序大白公众号❤️


    👇 👇👇

  • 相关阅读:
    java.lang.IllegalArgumentException: MALFORMED 解决方法
    MySQL数据库高级查询语句及案例
    hive解析json数据
    FANUC机器人Process IO接线及信号配置方法(二)
    JS前端实现身份证号码合法性校验(校验码校验)
    迷宫生成与路径规划算法-Python3.8-附Github代码
    HTML+CSS+JavaScript七夕情人节表白网页【樱花雨3D相册】超好看
    前端 按钮 loading效果阻断不了快速点击,执行防抖操作进行阻断接口连续调用
    为国产信创服务器提供LDAP统一身份认证方案
    单端信号转差分信号
  • 原文地址:https://blog.csdn.net/weixin_46931877/article/details/125943786