数据结构之并查集

简介: 并查集

并查集


并查集被认为是最简洁而优雅的数据结构之一,主要用于解决一些元素分组的问题。它管理一系列不相交的集合,并支持两种操作:

  • 合并(Union):把两个不相交的集合合并为一个集合。
  • 查询(Find):查询两个元素是否在同一个集合中。

1654597029927.png


Quick Find


第一个版本的并查集


public interface UnionFind {
        boolean isConnected(int p,int q);
        void unionElements(int p ,int q);
        int getSize();
}
public class UnionFind1 implements UnionFind {
        private int[] id;
        public UnionFind1(int size) {
            id = new int[size];
            for (int i = 0; i < id.length; i++) {
                id[i] = i;
            }
        }
        //查看元素p和元素q是否属于一个集合
        @Override
        public boolean isConnected(int p, int q) {
            return find(p) == find(q);
        }
        //合并元素p和元素q所属的集合
        @Override
        public void unionElements(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;
                }
            }
        }
        @Override
        public int getSize() {
            return id.length;
        }
        // 查找元素p所对应的集合编号
        private int find(int p) {
            if (p < 0 || p >= id.length) {
                throw new IllegalArgumentException("p is out of bound.");
            }
            return id[p];
        }
}
操作 时间复杂度
unionElements(p,q) O(n)
isConnected(p,q) O(1)


Quick Union


我们可以将每一个元素都看作是一个节点。


1654596810788.png


如果节点3想要连接节点2,那就是节点3去连接节点2,而2又指向自己


1654596968284.png


如果节点1想要连接节点3也是需要连接节点2即可。如果另一个节点的7想要连接2也是需要当前节点的根节点去连接2即可。


1654596860300.png


一开始的时候使用数组表示,每一个节点都是根节点和其他节点无关联。


1654596914363.png


如果我们想union 4,3 节点,我们只需要让4指向3即可。


1654596968284.png

1654597029927.png


如果想union3,8其实也非常简单,只需要用3指向8的节点


1654597184700.png



如果想union9,4其实并不是指向4这个节点,而是指向4的根节点的8。


1654597227787.png

public class UnionFind2 implements UnionFind{
    private int[] parent;
    public UnionFind2(int size) {
        parent = new int[size];
        for (int i = 0; i < size; i++) {
            parent[i] = i;
        }
    }
    //查看元素p和元素q是否属于一个集合
    // O(h)复杂度,h为树的高度
    @Override
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }
    //合并元素p和元素q所属的集合
    // O(h)复杂度,h为树的高度
    @Override
    public void unionElements(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);
        if (pRoot == qRoot) {
            return;
        }
        parent[pRoot] = qRoot;
    }
    @Override
    public int getSize() {
        return parent.length;
    }
    //查找过程,查找元素p所对应的集合编号
    // O(h)复杂度,h为树的高度
    private int find(int p){
        if (p < 0 || p >= parent.length) {
            throw new IllegalArgumentException("p is out of bound.");
        }
        while(p != parent[p]){
            p = parent[p];
        }
        return p;
    }
}
操作 时间复杂度
unionElements(p,q) O(h)
isConnected(p,q) O(h)


基于Size的优化


如果我们union 0,1 然后 union 0,2 然后 union 0,3这样的话就会产生一定的问题,因为我们没有对合并的元素的树没有做判断,所以会导致我们不断增加树的高度,从而成链表的结构。


1654597467970.png


如果我们的树是这样子的。


1654597503150.png


我们想union 4,9的话,我们的树就会变成这个样子。深度就达到了4。


1654597533789.png


但其实我们可以让9来指向4的根节点也就是8。这样我们的深度就只有3。


1654597636833.png

public class UnionFind3 implements UnionFind{
    private int[] parent;
    //sz[i] 表示以i为根的集合中元素个数
    private int[] sz;
    public UnionFind3(int size) {
        parent = new int[size];
        sz = new int[size];
        for (int i = 0; i < size; i++) {
            parent[i] = i;
            sz[i] = 1;
        }
    }
    //查看元素p和元素q是否属于一个集合
    // O(h)复杂度,h为树的高度
    @Override
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }
    //合并元素p和元素q所属的集合
    // O(h)复杂度,h为树的高度
    @Override
    public void unionElements(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);
        if (pRoot == qRoot) {
            return;
        }
        if(sz[pRoot] < sz[qRoot]){
            parent[pRoot] = qRoot;
            sz[qRoot] += sz[pRoot];
        }else{
            parent[qRoot] = pRoot;
            sz[pRoot] += sz[qRoot];
        }
    }
    @Override
    public int getSize() {
        return parent.length;
    }
    //查找过程,查找元素p所对应的集合编号
    // O(h)复杂度,h为树的高度
    private int find(int p){
        if (p < 0 || p >= parent.length) {
            throw new IllegalArgumentException("p is out of bound.");
        }
        while(p != parent[p]){
            p = parent[p];
        }
        return p;
    }
}


基于Rank的优化


1654597674899.png


假设现在有这样一棵树,我们进行union4,2根据size优化我们会把8来指向7。现在深度就变为了4。


1654597703476.png


但是这样子的话,原本的高度是2一下就变为了4,为了优化其实我们可以将7来指向8。深度就变为了3。我们需要将深度低的指向深度高的树


1654597859947.png

public class UnionFind4 implements UnionFind{
    private int[] parent;
    //rank[i] 表示以i为根的集合中树的层数
    private int[] rank;
    public UnionFind4(int size) {
        parent = new int[size];
        rank = new int[size];
        for (int i = 0; i < size; i++) {
            parent[i] = i;
            rank[i] = 1;
        }
    }
    //查看元素p和元素q是否属于一个集合
    // O(h)复杂度,h为树的高度
    @Override
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }
    //合并元素p和元素q所属的集合
    // O(h)复杂度,h为树的高度
    @Override
    public void unionElements(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);
        if (pRoot == qRoot) {
            return;
        }
        //根据两个元素所在树的rank不同判断合并方向
        //将rank低的集合合并到rank高的集合上
        if(rank[pRoot] < rank[qRoot]) {
            parent[pRoot] = qRoot;
        }else if(rank[qRoot] < rank[pRoot]){
            parent[qRoot] = pRoot;
        }else{
            parent[qRoot] = pRoot;
            rank[pRoot] += 1;
        }
    }
    @Override
    public int getSize() {
        return parent.length;
    }
    //查找过程,查找元素p所对应的集合编号
    // O(h)复杂度,h为树的高度
    private int find(int p){
        if (p < 0 || p >= parent.length) {
            throw new IllegalArgumentException("p is out of bound.");
        }
        while(p != parent[p]){
            p = parent[p];
        }
        return p;
    }
}

路径压缩


下图中三种树的操作其实都是一样的,但是左边的深度达到了5,而中间可只有2。我们应该如何进行路径压缩?


1654597886347.png


这样的一棵树结构下,如果我们进行下面操作

parent[p] = parent[parent[p]];

1654597907750.png


我们让4节点来指向父节点的父节点,也就变成了下面这样。


1654597947050.png


然后我们再让4的父节点执行同样操作就会变成这样。


1654597973791.png

//查找过程,查找元素p所对应的集合编号
    // O(h)复杂度,h为树的高度
    private int find(int p){
        if (p < 0 || p >= parent.length) {
            throw new IllegalArgumentException("p is out of bound.");
        }
        while(p != parent[p]){
            //路径压缩
            parent[p] = parent[parent[p]];
            p = parent[p];
        }
        return p;
    }
相关文章
|
4月前
|
容器
数据结构:并查集
数据结构:并查集
53 0
数据结构:并查集
|
4月前
|
机器学习/深度学习 存储
数据结构(九)---并查集
数据结构(九)---并查集
31 5
|
2天前
|
存储 C语言
数据结构基础详解(C语言): 树与二叉树的应用_哈夫曼树与哈夫曼曼编码_并查集_二叉排序树_平衡二叉树
本文详细介绍了树与二叉树的应用,涵盖哈夫曼树与哈夫曼编码、并查集以及二叉排序树等内容。首先讲解了哈夫曼树的构造方法及其在数据压缩中的应用;接着介绍了并查集的基本概念、存储结构及优化方法;随后探讨了二叉排序树的定义、查找、插入和删除操作;最后阐述了平衡二叉树的概念及其在保证树平衡状态下的插入和删除操作。通过本文,读者可以全面了解树与二叉树在实际问题中的应用技巧和优化策略。
|
2月前
|
算法 程序员 图形学
脑洞大开!Python并查集:用最简单的方式,解决最复杂的数据结构问题!
【7月更文挑战第17天】并查集,数据结构明星,处理不相交集合合并与查询。Python实现核心操作:查找与合并。路径压缩优化查找,按秩合并保持平衡。实战应用如图连通性判断,算法竞赛利器。掌握并查集,解锁复杂问题简单解法,照亮编程之旅!
42 10
|
2月前
|
Python
告别低效!Python并查集:数据结构界的超级英雄,拯救你的编程人生!
【7月更文挑战第18天】并查集,数据结构超级英雄,用于不相交集合的合并与查询。Python实现包括初始化、查找根节点和合并操作。应用广泛,如社交网络分析、图论问题、集合划分等。示例代码展示了解决岛屿数量问题,统计连通的“1”单元格数。掌握并查集,提升编程效率,解决复杂问题。
40 6
|
2月前
|
存储 Python
震惊!Python并查集:解锁数据结构新姿势,让你从菜鸟秒变大神!
【7月更文挑战第18天】并查集,一种处理不相交集合的树形数据结构,支持Union(合并)和Find(查询)操作。Python实现中,用字典存储元素及其父节点,初始时每个元素为根。通过路径压缩提高效率。应用包括网络连通性判断、动态连通性检测和集合操作。掌握并查集,提升编程技能,解决复杂问题。开始探索,成为数据结构大师!
31 5
|
2月前
|
Python
逆天改命!掌握Python并查集,数据结构难题从此不再是你的痛!
【7月更文挑战第18天】并查集,一种神器数据结构,用于处理不相交集合合并与查询,解决网络连通性等难题。Python实现常通过记录元素父节点
31 4
|
2月前
|
算法 数据挖掘 计算机视觉
Python并查集实战宝典:从入门到精通,让你的数据结构技能无懈可击!
【7月更文挑战第17天】并查集,如同瑞士军刀,是解决元素分组问题的利器,应用于好友关系、像素聚类、碰撞检测和连通性分析等场景。本文从基础到实战,介绍并查集的初始化、查找与路径压缩、按秩合并,以及在Kruskal算法中的应用。通过并查集,实现高效动态集合操作,对比哈希表和平衡树,其在合并与查找上的性能尤为突出。学习并查集,提升算法解决复杂问题的能力。
57 5
|
2月前
|
存储 算法 程序员
庆祝吧!掌握Python并查集,数据结构难题将不再是你的拦路虎!
【7月更文挑战第17天】并查集,一种数据结构,用于不相交集合的合并与查询,尤其适合解决图的连通性问题。通过Python实现,使用列表存储元素的父节点以判断集合关系。基本操作包括查找(确定元素集合)和合并(组合集合)。示例展示了如何用并查集配合Kruskal算法构建最小生成树。掌握并查集能高效处理复杂问题,优化后的查找和合并操作接近O(1)复杂度,是解决算法挑战的利器。
33 4
|
2月前
|
算法 计算机视觉 开发者
燃爆全场!Python并查集:数据结构界的网红,让你的代码炫酷无比!
【7月更文挑战第16天】并查集,Python中的效率明星,处理不相交集合合并与查询。用于社交网络分析、图像处理、图论算法等领域。优雅实现结合路径压缩和按秩合并
28 1