数据结构与算法:学习并查集

       并查集一般都不怎么用到,甚至听都没怎么听到过这么一个数据结构,写这篇文章是源自于我在刷力扣130. 被围绕的区域这道题的时候涉及到并查集的使用,大家如果没做过可以去做一下,程序员闲的时候刷刷算法题是非常有意义的事情。原本这道题使用深度优先遍历(DFS)广度优先遍历(BFS)就可以解决,但是执行时间才超过百分之五十多,这让我有点不服,我想看下使用并查集看下执行时间需要多久,所以想通过学习并查集来试试能不能有提升空间,如果你有一样的想法那就开始我们这篇文章的学习吧!

一、并查集实际用途

       并查集我们可以看作是是一种不一样的树形结构,广泛与图相关内容相结合使用能够高效的解决图相关的算法。并查集主要用在两个方面连接问题和数学中的集合类实现,更多的是应用在前者,比如网络中节点间的连接状态,这个网络是一个抽象的概念,连接状态可以理解为是否相连。

连接问题

       并查集主要应用在能高效地解决连接问题,我们来解释一下什么是连接问题,连接问题简要描述就是某两个点(这是一个抽象的概念)是否有一条线路把它们连接在一起,换句话说比如AB两个地点,A是否可以通过一条线路最终到达B,并查集这样的数据结构就能快速高效的判断这两地点的连通性问题,也就是能快速回答AB是连接还是不是连接的,我已经解释的非常直白了。

       我们借助关于图相关的内容来让大家体会一下,有时候需要判断图的两个顶点是否可达,图这样的数据结构广泛应用在人际关系、地图等等领域,这样的应用在计算机当中就像是形成一个“网络”的形式,而我们实际当中经常需要检查“网络”中节点间的连接状态。我们举两个例子但远远不局限于这两个例子:

  • 拿人际关系的应用来说,微信好友关系就可以通过图来实现,每个人就是图中的一个顶点,如果两个人是好友关系我们就用一条边把这两个顶点相连来建立好友关系,那现在就有一个实际的问题:任意的两个人,他们是否可以通过好友关系认识呢?这就是一个典型的连接问题。
  • 再举个地图的应用例子,比如判断两个地点是否有路可达,在地图上某两个地点,如果地点相隔比较近,我们很快能判断是否有一条路可达,那如果两个地点在地图上非常非常远,一个在中国一个在俄罗斯,那放到计算机里面计算机如何高效快速的告诉我们这两地是否有一条路连接可达呢?注意这里的连接问题只是问两点是否可达,和图的路径问题有所区别,路径问题是需要回答具体路径。

这些实际的连接问题都可以借助并查集来高效的解决。

二、并查集的实现

       并查集主要的两个操作就是:合并两个元素union(p, q); 查询元素在哪个连接组find(p);  通过这两个功能操作我们就可以用isConnected(p, q); 来回答p和q元素是否相连接在一起。我们从简单的实现开始,一步一步优化并查集。

第一版:

本文我们用的是数组来实现并查集,先通过画图来看看并查集的基本数据表示 

我们用下标表示元素,分别是0-9个元素,对应的元素数组值就是所在的分组,那从上面图示可以得出0-4这几个元素在一组,5-9这几个元素在一组,也就是相连的元素就意味着他们的数组值是相同的。我们现在实现代码:

public class UnionFind1 {
    private int[] id; // 用数组来实现

    public UnionFind1(int size){
        id = new int[size];

        // 初始时候所有元素自己是一组
        for (int i = 0; i < size; i++){
            id[i] = i;
        }
    }

    public int find(int p){
        return id[p];
    }

    public boolean isConnected(int p, int q){
        return find(p) == find(q);
    }

    public void union(int p, int q){
        int pID = find(p);
        int qID = find(q);

        if (pID == qID) return;

        // 如果某个元素合并到另一组,那该元素所在的其他元素都需要一起合并到另外一个组,这个应该不难理解吧?
        for (int i = 0; i < id.length; i++){
            if (id[i] == pID){
                id[i] = qID;
            }
        }
    }
}

第一版的并查集实现,其实效率非常不乐观,当执行上十万的数据量的时候非常耗时,大家可以测试一下执行时间。主要的耗时就是在执行union方法的时候,该方法是O(n)时间复杂度,但是外部n次调用union方法的话,总的时间复杂度就成了O(n^2),所以数据量越大就会越来耗时。

第二版:

第一版的实现方式对于查find非常快,但是对于并union的效率不是特别高,现在我们来学习下前人对并查集的另外一种实现思路,这种也就是并查集的常规实现思路。该种思路我们也用数组这种数据结构来实现,不同的是是将每个元素构建成一颗类似树一样的结构,我们通过画图来分析:

和树不同的是所有的节点都是指向父亲节点,根节点指向自己,上图就是两个分组,上面1-3是连通在一起属于一个组,他们共有的根节点是1,另外一组4-7属于一组,共有的根节点是4,这种思路判断某两个元素是否可达的方式就是看他们的根节点是否相同,如果相同则说明可达,不相同则不可达。如果我们要合并两个分组中任意一个元素的话,只需要将其中一个组的根节点指向另一个分组的根节点就实现了合并操作,相比第一版本实现思路是不是效率非常高了?因为我们不用将分组内的所有其他元素都更新以便,我们看下并操作之后的图:

这种思路其实可以用树的方式来实现,但是我们这里依然用数组来实现这种思路,注意上面思路是每个元素都是指向自己的父亲节点,如果要找根节点就继续向父亲的父亲一直循环寻找,我们用上面合并后的图来看数组最终是怎么存放的:

  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值