【数据结构】--并查集

这篇具有很好参考价值的文章主要介绍了【数据结构】--并查集。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、概念

​编辑

二、应用场景--“连接”问题(属于同一Qu

三、实现思路

 四、如何存储数据

五、定义接口

1.初始化(init)

2.其他

isSame()

六、抽象类

六、Quick Find【v1 所在集合的所有元素都指向 v2 的根节点】

1.Union

1.Union图解

2.注意点: 

3.代码实现

2.find 

1.find图解

2.代码实现

3.完整代码

七、(常用)Quick Union【v1 的根节点指向 v2 的根节点】

1.Union

1.注意点

2.Quick Union图解

3.代码实现

2.find

代码实现

3.完整代码

八、Quick Union的优化

1.简介

 2.方案一:基于size的优化【元素少的树 嫁接到 元素多的树】

​编辑 

3.方案二:基于rank的优化【矮的树 嫁接到 高的树】

九、路径压缩(Path Compression)

1.什么是路径压缩?

2.代码实现

十、路径分裂(Path Spliting)

1.概念

 2.代码实现

十一、路径减半(Path Halving)

1.概念

2.代码实现

十二、总结


一、概念

并查集(Disjoint Set)是一种可以动态维护若干个不重叠的集合,并支持合并与查询两种操作的一种数据结构。按照一般的思路,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中,这样时间复杂度就太高啦。而并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。

并查集支持查找一个元素所属的集合以及两个元素各自所属的集合的合并等运算,当给出两个元素的一个无序对(a,b)时,需要快速“合并” a 和 b 分别所在的集合,这期间需要反复“查找”某元素所在的集合。“并”、“查”和“集” 3 个字由此而来。在这种数据类型中,n个不同的元素被分为若干组。每组是一个集合,这种集合叫分离集合,称之为并查集。

【数据结构】--并查集

二、应用场景--“连接”问题(属于同一Qu

并查集是一种非常精巧而实用的数据结构,它注意用于处理一些不相交集合的合并温问题。一些常见的用途有连通子图,求最小生成树Kruskal算法和求最近公共祖先(LCA)。

三、实现思路

【数据结构】--并查集

 四、如何存储数据

假设并查集处理的数据都是整型,那么可以用整型数组来存储数据。

  • 数组索引代表元素值
  • 索引对应的值代表这个元素的根节点

【数据结构】--并查集

并查集是可以用数组实现的树形结构(二叉堆、优先级队列也是可以用数组实现的树形结构) 

五、定义接口

1.初始化(init)

初始化时,每个元素各自属于一个单元素集合

private int[] parents;
public UnionFind(int capacity){
	if(capacity < 0){
		throw new IllegalArgumentException("capacity must >= 1");
	}
	parents = new int[capacity];
	for (int i = 0; i < parents.length; i++) {
		parents[i] = i;
	}
}

【数据结构】--并查集

2.其他

/**
 * 查找v所属的集合(根结点)
 */
public abstract int find(int v);
/**
 * 合并v1、v2所在的集合
 */
public abstract void union(int v1, int v2);
/**
 * 检查v1、v2是否属于同一集合
 */
public boolean isSame(int v1, int v2);

isSame()

public boolean isSame(int v1, int v2){
	return find(v1) == find(v2);
}

六、抽象类

这是个并查集的抽象类,后面的所有并查集都将继承它。

package union;

/**
 * 这是个并查集的抽象类,后面的所有并查集都将继承它。
 */
public abstract class UnionFind {
//    初始一维数组存放父节点
    protected int[] parents;
    //初始化
    public UnionFind(int capacity){
        if (capacity<0){
            throw new IllegalArgumentException("capacity must be >=1");
        }
//        创建一个capacity的数组容量大小存放父节点
        parents=new int[capacity];
        //将初始化,自己作为父节点
        for (int i=0;i<parents.length;i++){
            parents[i]=i;
        }
    }

    public abstract void union(int v1,int v2);

    /**
     * 检查v1,v2是否属于同一个集合
     * @param v1
     * @param v2
     * @return
     */
    public boolean isSame(int v1,int v2){
        return find(v1)==find(v2);
    }

    /**
     * 查找v所属的集合(根节点)
     * @param v
     * @return
     */
    public abstract int find(int v);

//    判断v是否越界
    protected void rangeCheck(int v){
        if (v<0 || v>=parents.length){
            throw new IllegalArgumentException("v is out of bounds");
        }
    }
}

六、Quick Find【v1 所在集合的所有元素都指向 v2 的根节点】

1.Union

1.Union图解

【数据结构】--并查集

【数据结构】--并查集

2.注意点: 

1)将v1的根节点修改成v2的根节点,同时要将v1的根节点的根节点全部修改为v2的根节点。

2)union 时间复杂度:O(n)

3)树的高度最高为2

3.代码实现

/**
 * 将v1所在集合的所有元素都嫁接到v2的父节点上
 * v1    v2   union(v1,v2)
 *  0    4	     4
 * 1 2   3     0 1 2 3
 */
public void union(int v1, int v2){
	int p1 = parents[v1];
	int p2 = parents[v2];
	
	for (int i = 0; i < parents.length; i++) {
		if(parents[i] == p1){
//如果跟p1是同一个父节点,则将其父节点修改为p2的
			parents[i] = p2;
		}
	}
}

2.find 

1.find图解

【数据结构】--并查集

2.代码实现

    /**
     * 查找v所属的集合(根节点)
     * @param v
     * @return
     */
    public int find(int v){
        rangeCheck(v);
        return parents[v];
    }

3.完整代码

UnionFind_QuickFind 

package union;

/**
 * 查找(Find)的时间复杂度:O(1)
 * 合并(Union)的时间复杂度:O(n)
 */

public class UnionFind_QuickFind extends UnionFind{
    public UnionFind_QuickFind(int capacity) {
        super(capacity);
    }

    /**
     * 查找v所属的集合(根节点)
     * @param v
     * @return
     */
    public int find(int v){
        rangeCheck(v);
        return parents[v];
    }
    public void union(int v1,int v2){
//        p1:表示v1的父节点
        int p1=find(v1);
//        p2:表示v2的父节点
        int p2=find(v2);
        if (p1==p2) return;

//        到此处则v1和v2不是同一个父节点
        for (int i=0;i<parents.length;i++){
//            遍历所有的父节点,如果该父节点和v1表示,要将其全部修改为v2的父节点(p2)
            if (parents[i]==p1){
                parents[i]=p2;
            }
        }
    }
}

七、(常用)Quick Union【v1 的根节点指向 v2 的根节点】

1.Union

1.注意点

1)Quick Find 的 union(v1, v2):让 v1 所在集合的所有元素都指向 v2 的根节点

2)Quick Union 的 union(v1, v2):让 v1 的根节点指向 v2 的根节点 。(与Quick Find进行对比)

3)树的最大高度超过2

2.Quick Union图解

【数据结构】--并查集

 【数据结构】--并查集

3.代码实现

/**
 * 将v1的根节点嫁接到v2的根节点上
 */
    @Override
    public void union(int v1, int v2) {
        int p1=find(v1);
        int p2=find(v2);
        if (p1==p2){
            return;
        }
//        将p1指向p2的父节点【也就是p1的父节点为p2的父节点】
        parents[p1]=p2;
    }

2.find

【数据结构】--并查集

当前节点值和父节点的值相等的时候,说明该节点是根节点【v==parents[i]】

代码实现

   /**
     * 通过parents链表不断向上查找,直到根节点
     * @param v
     * @return
     */   
 @Override
    public int find(int v) {
        rangeCheck(v);
        //当v不跟父节点的值相等,则表示还未找到根节点
        while (v!=parents[v]){
            v=parents[v];
        }
        return v;
    }

3.完整代码

QuionFind_QuickUnion
package union;

/**
 * 查找(Find)的时间复杂度:O(logn), 可以优化至 O(𝛼(𝑛)), α(𝑛) < 5
 * 合并(Union)的时间复杂度:O(logn), 可以优化至 O(𝛼(𝑛)), α(𝑛) < 5
 */

public class QuionFind_QuickUnion extends UnionFind{
    public QuionFind_QuickUnion(int capacity) {
        super(capacity);
    }

    /**
     * 将v1所在的根节点 嫁接到v2的根节点上
     * @param v1
     * @param v2
     */
    @Override
    public void union(int v1, int v2) {
        int p1=find(v1);
        int p2=find(v2);
        if (p1==p2){
            return;
        }
//        将p1指向p2的父节点【也就是p1的父节点为p2的父节点】
        parents[p1]=p2;
    }

    /**
     * 通过parents链表不断向上查找,直到根节点
     * @param v
     * @return
     */
    @Override
    public int find(int v) {
        rangeCheck(v);
        //当v不跟父节点的值相等,则表示还未找到根节点
        while (v!=parents[v]){
            v=parents[v];
        }
        return v;
    }
}

八、Quick Union的优化

1.简介

【数据结构】--并查集

 2.方案一:基于size的优化【元素少的树 嫁接到 元素多的树】

不是固定的让某一棵树嫁接到另一棵树,让元素少的树 嫁接到 元素多的树

package union;

/**
 * Quick Union--基于size的优化
 */
public class UnionFind_QuickUnion_Size extends UnionFind{
//    该数组存放以size[i]为根节点的节点个数
    private int[] sizes;
    public UnionFind_QuickUnion_Size(int capacity) {
        super(capacity);
        sizes=new int[capacity];
        for (int i=0;i<sizes.length;i++){
//            初始化一个根节点只有一个节点【就是它本身】
            sizes[i]=1;
        }
    }

    /**
     * 将v1所在的根节点 嫁接到v2的根节点上
     * @param v1
     * @param v2
     */
    @Override
    public void union(int v1, int v2) {
        int p1=find(v1);
        int p2=find(v2);
        if (p1==p2){
            return;
        }

        //如果v1所在的树的长度<v2所在的树的长度,则将v1嫁接到v2上
        if (sizes[p1]<sizes[p2]){
            parents[p1]=p2;
            sizes[p2]+=sizes[p1];
        }else { //如果v1所在的树的长度>v2所在的树的长度,则将v2嫁接到v1上
            parents[p2]=p1;
            sizes[p1]+=sizes[p2];
        }
    }

    /**
     * 通过parents链表不断向上查找,直到根节点
     * @param v
     * @return
     */
    @Override
    public int find(int v) {
        rangeCheck(v);
        //当v不跟父节点的值相等,则表示还未找到根节点
        while (v!=parents[v]){
            v=parents[v];
        }
        return v;
    }
}

 

3.方案二:基于rank的优化【矮的树 嫁接到 高的树】

【数据结构】--并查集

package union;

/**
 * Quick Union--基于rank的优化
 */
public class UnionFind_QuickUnion_Rank extends UnionFind{
//    该数组存放以rank[i]为存放高度
    private int[] ranks;
    public UnionFind_QuickUnion_Rank(int capacity) {
        super(capacity);
        ranks=new int[capacity];
        for (int i=0;i<ranks.length;i++){
//            初始化一个根节点只有一个节点【就是它本身】
            ranks[i]=1;
        }
    }

    /**
     * 将v1所在的根节点 嫁接到v2的根节点上
     * @param v1
     * @param v2
     */
    @Override
    public void union(int v1, int v2) {
        int p1=find(v1);
        int p2=find(v2);
        if (p1==p2){
            return;
        }

        if(ranks[p1] < ranks[p2]){ //从矮的到高的树的总体高度不变
            parents[p1] = p2;
        }else if(ranks[p1] > ranks[p2]){
            parents[p2] = p1;
        }else{ // ranks[p1] == ranks[p2]
            //谁嫁接谁都可以
            parents[p1] = p2;//嫁接到p2
            ranks[p2] += 1;
        }
    }

    /**
     * 通过parents链表不断向上查找,直到根节点
     * @param v
     * @return
     */
    @Override
    public int find(int v) {
        rangeCheck(v);
        //当v不跟父节点的值相等,则表示还未找到根节点
        while (v!=parents[v]){
            v=parents[v];
        }
        return v;
    }
}

九、路径压缩(Path Compression)

虽然有了基于 rank 的优化,树会相对平衡一点,但是随着 union 次数的增多:树的高度依然会越来越高,导致 find 操作变慢,尤其是底层节点 (因为 find 是不断向上找到根节点) 。

1.什么是路径压缩?

在 find 时使路径上的所有节点都指向根节点

【数据结构】--并查集

 【数据结构】--并查集

2.代码实现

该类继承了 UnionFind_QU_R,表明它是在 Quick Union 的 rank 优化的基础上,再优化 

package union;

/**
 * Quick Union - 基于rank的优化 - 路径压缩(Path Compression)
 */
public class UnionFind_QuickUnion_rank_PathCompression extends UnionFind_QuickUnion_Rank{
    public UnionFind_QuickUnion_rank_PathCompression(int capacity) {
        super(capacity);
    }

    @Override
    public int find(int v) { //v==1,parents[v]==2
        rangeCheck(v);
        if (parents[v]!=v){ //表示还没有找到根节点
            parents[v]=find(parents[v]);//将根节点赋值
        }
        return parents[v];
    }
}

十、路径分裂(Path Spliting)

1.概念

使路径上的每个节点都指向其祖父节点(parent的parent)

【数据结构】--并查集

 2.代码实现

该类继承了 UnionFind_QU_R,表明它是在 Quick Union 的 rank 优化的基础上

package union;

/**
 * Quick Union - 基于rank的优化 - 路径分裂(Path Spliting)
 */
public class UnionFind_QuickUnion_rank_PathSpliting extends UnionFind_QuickUnion_Rank{
    public UnionFind_QuickUnion_rank_PathSpliting(int capacity) {
        super(capacity);
    }

    @Override
    public int find(int v){
        rangeCheck(v);
        while(v != parents[v]){
//            将2保留下来,如果不保留,则2直接消失
            int p = parents[v];
//            parents[parents[v]]:找到v的祖父节点
            parents[v] = parents[parents[v]];
            v = p;
        }
        return parents[v];
    }
}

十一、路径减半(Path Halving)

1.概念

使路径上每隔一个节点就指向其祖父节点(parent的parent)

【数据结构】--并查集

 文章来源地址https://www.toymoban.com/news/detail-408138.html

2.代码实现

该类继承了 UnionFind_QU_R,表明它是在 Quick Union 的 rank 优化的基础上,再优化

package union;

/**
 *  Quick Union - 基于rank的优化 - 路径减半(Path Halving)
 */
public class UnionFind_QuickUnion_rank_PathHalving extends UnionFind_QuickUnion_Rank{
    public UnionFind_QuickUnion_rank_PathHalving(int capacity) {
        super(capacity);
    }

    @Override
    public int find(int v){
        rangeCheck(v);
        while(v != parents[v]){
            parents[v] = parents[parents[v]];
            v = parents[v];
        }
        return v;
    }
}

十二、总结

【数据结构】--并查集

 

到了这里,关于【数据结构】--并查集的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】--并查集

    目录 一、概念 ​编辑 二、应用场景--“连接”问题(属于同一Qu 三、实现思路  四、如何存储数据 五、定义接口 1.初始化(init) 2.其他 isSame() 六、抽象类 六、Quick Find【v1 所在集合的所有元素都指向 v2 的根节点】 1.Union 1.Union图解 2.注意点:  3.代码实现 2.find  1.find图

    2023年04月09日
    浏览(31)
  • 数据结构详细笔记——并查集

    集合:将各个元素划分为若干个互不相交的子集的集合 森林是m(m=0)棵互不相交的树的集合 优化思路:在每次Union操作构建树的时候,尽可能让树不长高 ①用根结点的绝对值表示树的结点的总数 ②Union操作,让小树合并到大树 优化思路:先找到根结点,再将查找路径上所有结

    2024年02月06日
    浏览(38)
  • 数据结构之并查集

    并查表原理是一种 树型的数据结构 ,用于处理一些不相交集合的合并及查询问题。并查集的思想是用一个数组表示了整片森林(parent),树的根节点唯一标识了一个集合,我们只要找到了某个元素的树根,就能确定它在哪个集合里。这类问题的抽象数据类型称为并查集(uni

    2024年02月12日
    浏览(36)
  • 【数据结构与算法】并查集

    并查集是一个树形结构,所谓的并查,就是 当我们有了一个节点,我们就能知道这个节点属于哪个集合 。 举个例子理解以下:战国时期有很多国家,它们会互相打仗,那么现在有两个互相不认识的人,如何知道对方是敌是友呢? 现在有一种方法:每个国家都有一个大王,我

    2023年04月15日
    浏览(36)
  • 算法与数据结构(九)--并查集

    1.处理对象:Disjoint Set,即“不相交集合”。 在一些应用问题中,需将n个不同的元素划分成一组不相交的集合。开始时,每个元素自成一个单元素集合,然后按一定顺序将属于同一组元素的集合合并。其间要反复用到查询某个元素属于哪个集合的运算。适合于描述这类问题的

    2024年02月10日
    浏览(40)
  • 【数据结构】| 并查集及其优化实现

    以一个直观的问题来引入并查集的概念。 亲戚问题:有一群人,他们属于不同家族,同一个家族里的人互为亲戚,不同家族的人不是亲戚。随机指定两个人,问他们是否有亲戚关系。 以下图3个不相交的集合表示 3 个家族,当询问两个人是否有亲戚关系时,也就是问两个元素

    2024年02月09日
    浏览(37)
  • 数据结构(八):并查集详解 (多图+动图)

    目录 一、什么是并查集 二、并查集的存储结构 三、并查集的基本操作 (一)初始化 (二)Find操作 (三)Union操作 四、并查集的优化 (一)Union操作优化(小树并入大树) (二)Find操作优化(压缩路径)         并查集的逻辑结构是一个包含N个元素的 集合 ,如图:

    2024年02月03日
    浏览(43)
  • 计算机基础--->数据结构(9)【并查集】

    并查集是一种用于解决集合合并和查询问题的数据结构,主要用于实现有关集合的操作,它有两种主要操作,合并(union)和查找(find)。 查找(Find):用来确定元素属于哪个集合。它接受一个元素作为参数,并返回这个元素所属集合的代表元素。通过查找操作,可以判断

    2024年02月15日
    浏览(47)
  • Java高阶数据结构 & 并查集 & 最小生成树

    并查集与最小生成树 1.1 并查集的原理 在一些应用问题中,我们常常会遇到一类问题 一开始是一个人 后来新增的人可能与这个人有关系,也可能与这个人无关系。 一个人与一个人有关系,这个人与另一个人也有关系,那么三人都有关系。 有关系的和没关系的之间是不同的类

    2024年02月03日
    浏览(40)
  • 数据结构--并查集的进一步优化

    压缩路径 − − F i n d 操作,先找到根节点,再将查找路径上所有结点都挂到根结点下 color{red}压缩路径 -- Find操作,先找到根节点,再将查找路径上所有结点都挂到根结点下 压缩路径 − − F in d 操作,先找到根节点,再将查找路径上所有结点都挂到根结点下 每次Find操作,

    2024年02月15日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包