IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 数据结构之并查集 -> 正文阅读

[数据结构与算法]数据结构之并查集

1. 并查集(Union Find)

也叫做不相交集合(Disjoint Set)

  • 核心操作
  1. 查找(Find):查找元素所在的集合;
  2. 合并(Union):将两个元素所在的集合合并为一个集合;

1.1 存储方式

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

  • 存储结构
    在这里插入图片描述
  • 逻辑结构
    在这里插入图片描述

可以看出:(0、1、3)是一个集合;(4、5、6、7)是一个集合;2自己一个集合。

1.2 抽象类设计

/**
 * @Description UnionFind 抽象类
 * @date 2022/5/9 20:28
 */
public abstract class AbstractUnionFind {

    protected int[] parents;

    public AbstractUnionFind(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;
        }
    }

    /**
     * 检测索引是否合法
     * @param index
     */
    protected void rangeCheck(int index){
        if (index >= parents.length || index < 0){
            throw new IndexOutOfBoundsException("Index must less than array's length ");
        }
    }

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

    /**
     * 合并 v1 v2 的所属集合
     * @param v1
     * @param v2
     */
    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);
    }
}

1.3 初始化

在执行初始化的时候,将值的父节点指向自己表示自己为一个单独的集合。

在这里插入图片描述

    private int[] parents;

    public QuickFind(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;
        }
    }

1.4 Quick Find方式实现并查集

1.4.1 union

union(v1, v2)v1集合的所有元素都指向v2集合的根节点。

  • 初始化之后:在这里插入图片描述
  • union(1, 0):将1指向0的根节点。
    在这里插入图片描述
  • union(1,2):将1所指向节点的所有原始的根节点都改为2的根节点在这里插入图片描述
  • 代码实现:
    @Override
    public void union(int v1, int v2) {
        // 获取两个值所在的集合(根节点值)
        int p1 = find(v1);
        int p2 = find(v2);
        // 如果根节点相同 就表示位于同一个集合中不需要合并
        if (p1 == p2) return;

        // 遍历数组中所有的元素,如果根节点与 v1 相同的都改为 v2 的根节点
        for (int i = 0; i < parents.length; i++) {
            if (parents[i] == p1){
                parents[i] = p2;
            }
        }
    }

1.4.2 find

直接返回数组中存储的根节点即可。

    /**
     * 检测索引是否合法
     * @param index
     */
    private void rangeCheck(int index){
        if (index >= parents.length || index < 0){
            throw new IndexOutOfBoundsException("Index must less than array's length ");
        }
    }
    @Override
    public int find(int v) {
        rangeCheck(v);
        // 因为这里直接指向的就是根节点,所以直接返回即可
        return parents[v];
    }

1.4.3 总结

  1. union()的时间复杂度为O(n)
  2. find()的时间复杂度为O(1)

1.5 QuickUnion

1.5.1 union

union(v1, v2)v1集合的根节点都指向v2集合的根节点。

  • 初始化之后:在这里插入图片描述
  • union(1, 0):将1的根节点指向0的根节点:
    在这里插入图片描述
  • union(1,2):将1的根节点指向2的根节点:
    在这里插入图片描述
  • union(4,1):将4的根节点指向1的根节点:
    在这里插入图片描述
    @Override
    public void union(int v1, int v2) {
        int p1 = find(v1);
        int p2 = find(v2);
        if (p1 == p2) return;
        // 将当前节点的父节点改为要修改的节点的父节点
        parents[p1] = p2;
    }

1.5.2 find

    @Override
    public int find(int v) {
        rangeCheck(v);
        // 一直往上找 知道当前元素的父节点是自己就表示为根节点
        while (v != parents[v]){
            v = parents[v];
        }
        return v;
    }

1.5.3 优化

在进行union()时,可以会出现树不平很的情况,甚至有可能会退化成链表。

在这里插入图片描述

1.5.3.1 基于size的优化

元素少的树嫁接到元素多的树上。

在这里插入图片描述

/**
 * @Description QuickUnion size 优化版本
 * @date 2022/5/9 20:48
 */
public class QuickUnionSize extends AbstractUnionFind{

    // 记录当前元素的数量
    private int[] sizes;

    public QuickUnionSize(int capacity) {
        super(capacity);
        sizes = new int[capacity];
        // 将当前元素的数量默认为 1
        for (int i = 0; i < sizes.length; i++) {
            sizes[i] = 1;
        }
    }

    @Override
    public int find(int v) {
        rangeCheck(v);
        // 一直往上找 知道当前元素的父节点是自己就表示为根节点
        while (v != parents[v]){
            v = parents[v];
        }
        return v;
    }

    @Override
    public void union(int v1, int v2) {
        int p1 = find(v1);
        int p2 = find(v2);
        if (p1 == p2) return;
        // 将数量少的嫁接到数量多的上面
        if (sizes[p1] < sizes[p2]){
            parents[p1] = p2;
            sizes[p2] += sizes[p1];
        }else {
            parents[p2] = p1;
            sizes[p1] += sizes[p2];
        }
    }
}

1.5.3.2 基于rank的优化

矮的树嫁接到高的树上。

在这里插入图片描述

/**
 * @Description QuickUnion rank 优化版本
 * @date 2022/5/9 20:48
 */
public class QuickUnionRank extends AbstractUnionFind{

    // 记录 '树' 的高度
    private int[] ranks;

    public QuickUnionRank(int capacity) {
        super(capacity);
        ranks = new int[capacity];
        // 将当前 ‘树’ 的高度默认为 1
        for (int i = 0; i < ranks.length; i++) {
            ranks[i] = 1;
        }
    }

    @Override
    public int find(int v) {
        rangeCheck(v);
        // 一直往上找 知道当前元素的父节点是自己就表示为根节点
        while (v != parents[v]){
            v = parents[v];
        }
        return v;
    }

    @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 {
            parents[p1] = p2;
            ranks[p2] += 1;
        }
    }
}

1.5.3.3 基于rank的优化 —— 路径压缩

find()时,将路径上的所有节点都指向根节点,从而降低树的高度。

在这里插入图片描述

/**
 * @Description rank优化 —— 路径压缩
 * @date 2022/5/9 21:11
 */
public class QuickUnionPC extends AbstractUnionFind{
    // 记录 '树' 的高度
    private int[] ranks;

    public QuickUnionPC(int capacity) {
        super(capacity);
        ranks = new int[capacity];
        // 将当前 ‘树’ 的高度默认为 1
        for (int i = 0; i < ranks.length; i++) {
            ranks[i] = 1;
        }
    }

    @Override
    public int find(int v) {
        rangeCheck(v);
        // 递归调用 只要当前元素的父节点不是自己就一直往上并且赋值为根节点
        if (v != parents[v]){
            parents[v] = find(parents[v]);
        }
        return parents[v];
    }

    @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 {
            parents[p1] = p2;
            ranks[p2] += 1;
        }
    }
}

1.5.3.4 基于rank的优化 —— 路径分裂

find()时,使路径上的每个节点都指向其祖父节点。

在这里插入图片描述

/**
 * @Description rank优化 —— 路径减半
 * @date 2022/5/9 21:11
 */
public class QuickUnionPS extends AbstractUnionFind{
    // 记录 '树' 的高度
    private int[] ranks;

    public QuickUnionPS(int capacity) {
        super(capacity);
        ranks = new int[capacity];
        // 将当前 ‘树’ 的高度默认为 1
        for (int i = 0; i < ranks.length; i++) {
            ranks[i] = 1;
        }
    }

    @Override
    public int find(int v) {
        rangeCheck(v);
        // 让元素的父节点改为祖父节点
        while (v != parents[v]){
            int p = parents[v];
            parents[v] = parents[parents[v]];
            v = p;
        }
        return parents[v];
    }

    @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 {
            parents[p1] = p2;
            ranks[p2] += 1;
        }
    }
}

1.5.3.5 基于rank的优化 —— 路径减半

find()时,使路径上每隔一个节点就指向其祖父节点。

在这里插入图片描述

/**
 * @Description rank优化 —— 路径减半
 * @date 2022/5/9 21:11
 */
public class QuickUnionPV extends AbstractUnionFind{
    // 记录 '树' 的高度
    private int[] ranks;

    public QuickUnionPV(int capacity) {
        super(capacity);
        ranks = new int[capacity];
        // 将当前 ‘树’ 的高度默认为 1
        for (int i = 0; i < ranks.length; i++) {
            ranks[i] = 1;
        }
    }

    @Override
    public int find(int v) {
        rangeCheck(v);
        // 让元素的父节点改为祖父节点
        while (v != parents[v]){
            parents[v] = parents[parents[v]];
            v = parents[v];
        }
        return v;
    }

    @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 {
            parents[p1] = p2;
            ranks[p2] += 1;
        }
    }
}

1.5.4 总结

  • 优化之前的:
  1. union()的时间复杂度为O(n)
  2. find()的时间复杂度为O(n)
  • 使用 rang或者size在添加以下三个中的一个路径分裂、路径减半、路径压缩之后:可以使两个操作的均摊时间复杂度优化为:O(α(n))其中α(n) < 5
  • 建议使用QuickUnion基于Rank优化再添加路径分裂或者路径减半。

1.6 自定义类型

  1. 使用方法将自定义类型转为整型(比如生成哈希值,但要保证哈希值不相同)。
  2. 链表 + map
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-05-10 12:08:32  更:2022-05-10 12:09:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 3:21:26-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码