蓝桥杯第十五届抱佛脚(八)并查集

news2025/4/11 13:02:42

蓝桥杯第十五届抱佛脚(八)并查集

基本概念

并查集是一种数据结构,用于管理一系列不交集的元素集合,并支持两种操作:

查找(Find):

查找操作用于确定某个元素属于哪个集合,这通常通过追溯元素的父节点直到找到代表元素来完成。

合并(Union):

合并操作用于将两个不相交的集合合并为一个。合并可以通过将一个集合的代表元素的父节点指针指向另一个集合的代表元素来完成。

具体实现时,并查集通过一个数组来维护每个元素的父指针,每个集合由一个根元素代表,该根元素的父指针指向自己,表明它是集合的代表。查找操作通过递归或迭代地跟随元素的父指针直到根元素来完成,而合并操作则是将一个集合的根元素的父指针指向另一个集合的根元素。

并查集的两种实现方式

并查集的两种基本实现:quick-findquick-union,这两种方法各有优缺点,并用于解决不相交集合的问题。

quick-find(基于 ID)

quick-find 策略中,每个元素都有一个唯一的标识符,我们称之为 id。在初始化时,每个元素的 id 都是不同的,代表它们各自独立的集合。

操作

  • Find:要判断两个元素是否在同一个集合中,只需比较它们的 id 是否相同。
  • Union:合并两个集合时,我们需要将一个集合中所有元素的 id 全部更新为另一个集合的 id

特点

  • 优点Find 操作非常快,时间复杂度为 O(1)。
  • 缺点Union 操作较慢,因为它需要遍历一个集合中的所有元素来更新 id,时间复杂度为 O(N)。

quick-union(基于 parent)

quick-union 方法使用一个 parent 数组来组织数据,parent[i] 存储的是第 i 个元素的父节点的索引。

操作

  • Find:通过连续追溯父节点直到找到根节点(根节点的特点是 parent[root] == root),这个根节点代表了集合的唯一标识。
  • Union:要合并两个元素所在的集合,只需要将一个集合的根节点的 parent 指向另一个集合的根节点。

特点

  • 优点Union 操作较快,时间复杂度近似为 O(1),只涉及更新一个节点的父节点引用。
  • 缺点Find 操作可能较慢,最坏情况下时间复杂度为 O(N),特别是在树形结构非常深的情况下。

并查集的存储结构

在Java中,一个并查集通常使用一个整数数组来存储。每个索引代表一个特定的元素,而该索引处的值则存储该元素的父节点索引。在并查集的初始状态下,每个元素的父节点索引指向它自己,表示每个元素构成一个单独的集合。这种结构很容易地支持并查集的两个基本操作:查找(Find)和联合(Union)。

以下是一个简单的Java实现,展示了并查集的存储结构和基本操作:

public class UnionFind {
    private int[] parent;  // 存储每个节点的父节点

    // 构造函数,初始化并查集
    public UnionFind(int size) {
        parent = new int[size];
        for (int i = 0; i < size; i++) {
            parent[i] = i; // 初始时,每个元素的父节点指向自己
        }
    }

    // 查找操作,找到元素p的根节点
    public int find(int p) {
        while (p != parent[p]) {
            parent[p] = parent[parent[p]]; // 路径压缩,直接指向根节点
            p = parent[p];
        }
        return p;
    }

    // 联合操作,将元素p和元素q所在的集合合并
    public void union(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        if (rootP != rootQ) {
            parent[rootP] = rootQ; // 将一个集合的根节点指向另一个集合的根节点
        }
    }
}

在这个类中:

  • parent 数组被用作并查集的主要数据结构。
  • UnionFind 构造函数初始化数据结构,设置每个元素的父节点为自己。
  • find 方法实现了路径压缩,使得每次查找操作后,路径上的所有节点都直接连接到根节点。
  • union 方法将两个元素所在的集合合并为一个集合。

按秩合并优化

当并查集的union操作非常频繁,且集合的元素数量很大时,如果不进行优化,通过union操作构造的树可能会非常不平衡,甚至退化成链状结构,这会导致后续的find操作的效率降低。

按秩合并是一种优化union操作的方法,它通过比较两个树的“秩”(通常是树的高度)来决定合并的方向,始终将较低秩的树合并到较高秩的树上,从而避免树的不必要高度增长。

操作步骤

  1. 初始化:对于每个元素,初始化它们的秩(rank)为0,并指定每个元素的父节点为其自身,表示每个元素独立成一个集合。
  2. 合并(Union)操作
    a. 在合并两个元素所在的集合前,首先找到它们各自的根节点(即集合的代表元素)。
    b. 比较两个根节点的秩(代表集合的树的高度)。
    c. 将秩较小的树的根节点指向秩较大的树的根节点。
    d. 如果两棵树的秩相同,选择其中一棵作为代表,将其秩加1。

代码示例

public class UnionFind {
    private int[] parent; // 父节点数组
    private int[] rank;   // 秩数组

    public UnionFind(int size) {
        parent = new int[size];
        rank = new int[size];
        for (int i = 0; i < size; i++) {
            // 初始化时,每个元素的父节点指向自身
            parent[i] = i;
            // 初始化时,每个元素的秩为0
            rank[i] = 0;
        }
    }

    // 查找元素x的根节点
    public int find(int x) {
        if (x != parent[x]) {
            parent[x] = find(parent[x]); // 路径压缩优化
        }
        return parent[x];
    }

    // 合并元素x和元素y所在的集合
    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);

        if (rootX != rootY) {
            if (rank[rootX] < rank[rootY]) {
                parent[rootX] = rootY; // 将秩较小的树的根指向秩较大的树的根
            } else if (rank[rootX] > rank[rootY]) {
                parent[rootY] = rootX;
            } else {
                parent[rootY] = rootX; // 如果秩相同,任意合并并更新秩
                rank[rootX]++;
            }
        }
    }

    // 判断两个元素是否在同一个集合中
    public boolean isConnected(int x, int y) {
        return find(x) == find(y);
    }
}

UnionFind 类实现了一个并查集,其中包括了unionfind操作,以及用于检查两个元素是否在同一个集合中的isConnected方法。在union方法中,我们使用了按秩合并的逻辑来优化树的结构。

路径压缩优化

路径压缩主要是针对find操作进行优化的。在没有优化的并查集中,find操作可能需要遍历很长的路径来找到根节点,特别是在union操作导致树高度增加的情况下。

路径压缩在执行find操作时,会将沿途的每个节点直接连接到根节点,这样不仅优化了当前的find操作,也加快了未来对这些节点的find操作。

操作步骤

  1. 查找(Find)操作:在执行查找操作时,递归地访问父节点直到找到根节点。
  2. 路径优化:在递归过程中,将访问过的每个节点直接连接到根节点上,这样可以减少后续操作中访问这些节点时的步骤数。

路径压缩通常和“按秩合并”结合使用,但即使单独使用路径压缩,也能显著提高并查集的效率。

代码示例

public class UnionFind {
    private int[] parent; // 父节点数组

    public UnionFind(int size) {
        parent = new int[size];
        // 初始化,每个节点的父节点是它自己
        for (int i = 0; i < size; i++) {
            parent[i] = i;
        }
    }

    // 查找元素p的根节点
    public int find(int p) {
        // 当我们不是根节点时
        while (p != parent[p]) {
            // 进行路径压缩
            parent[p] = parent[parent[p]];
            // 继续向上查找
            p = parent[p];
        }
        return p;
    }

    // 合并元素p和元素q所在的集合
    public void union(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        if (rootP != rootQ) {
            parent[rootP] = rootQ; // 任意连接两个根节点
        }
    }
}

我们定义了一个UnionFind类,它具有findunion方法。在find方法中,我们实现了路径压缩,通过在查找根节点的过程中更新节点的直接父节点为根节点,来减少后续操作的查找步骤。这种优化可以显著提升并查集的性能,特别是在处理大量findunion操作的时候。

并查集例题

2017年第八届蓝桥杯国赛大学C组真题 合根植物

题目描述:

w 星球的一个种植园,被分成 m×n 个小格子(东西方向 m 行,南北方向 n 列)。每个格子里种了一株合根植物。
这种植物有个特点,它的根可能会沿着南北或东西方向伸展,从而与另一个格子的植物合成为一体。
如果我们告诉你哪些小格子间出现了连根现象,你能说出这个园中一共有多少株合根植物吗?

输入描述:

第一行,两个整数 m,n,用空格分开,表示格子的行数、列数(1 ≤ m,n ≤ 1000)。
接下来一行,一个整数 k (0 ≤ k ≤ 100000 ),表示下面还有 k 行数据。
接下来 k 行,每行两个整数 a,b,表示编号为 a 的小格子和编号为 b 的小格子合根了。
格子的编号一行一行,从上到下,从左到右编号。
比如:5×4 的小格子,编号:
行列
1234
5678
9101112
13141516
17181920

输出描述:

输出植物数量。

输入输出样例:

示例:

输入:

5 4
16
2 3
1 5
5 9
4 8
7 8
9 10
10 11
11 12
10 14
12 16
14 18
17 18
15 19
19 20
9 13
13 17

输出:

5

样例图例如下:

图片描述

解题思路:

在本题中,我们把每个格子视为一个独立的集合,当两个格子“合根”时,我们将它们所在的集合进行合并。最后,计算并查集中独立集合的数量,即为不同的“合根植物”数量。

  1. 初始化并查集:我们创建一个并查集,其中每个格子作为一个独立的集合。在这个并查集中,每个格子最初的父节点是它自己。

  2. 处理合根关系:根据输入的合根关系,我们使用并查集的union方法将这些格子所在的集合进行合并。合并的基本原则是:如果两个格子合根,它们应属于同一个集合。

  3. 计算合根植物的数量:遍历所有格子,计算其父节点是自身的格子数量。这些格子代表了各个不相交集合的根节点,即不同的“合根植物”。

import java.util.Scanner;

class PlantUnionFind {
    private int[] parent; // 存储每个元素的父节点索引

    // 构造方法,初始化并查集
    public PlantUnionFind(int size) {
        parent = new int[size + 1];  // 从1开始编号,因此需要 size + 1 的空间
        for (int i = 1; i <= size; i++) {
            parent[i] = i; // 初始时,每个元素的父节点是它自己
        }
    }

    // 查找元素x的根节点(集合代表)
    public int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]); // 路径压缩优化
        }
        return parent[x];
    }

    // 合并元素x和y所在的集合
    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX != rootY) {
            parent[rootX] = rootY; // 将一个集合的根节点指向另一个集合的根节点
        }
    }

    // 计算并查集中独立集合的数量
    public int countPlants() {
        int count = 0;
        for (int i = 1; i < parent.length; i++) {
            if (parent[i] == i) {
                count++; // 如果节点的父节点是它自己,它是一个根节点
            }
        }
        return count;
    }
}

public class Main{
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int m = scanner.nextInt();
        int n = scanner.nextInt();
        int k = scanner.nextInt();
        PlantUnionFind unionFind = new PlantUnionFind(m * n);

        for (int i = 0; i < k; i++) {
            int a = scanner.nextInt();
            int b = scanner.nextInt();
            unionFind.union(a, b); // 处理合根关系
        }

        System.out.println(unionFind.countPlants()); // 输出合根植物数量
        scanner.close();
    }
}

2019年第十届蓝桥杯省赛大学A组真题 修改数组

题目描述: 给定一个长度为 N 的数组A [A1,A2,···,AN],数组中有可能有重复出现的整数。

现在小明要按以下方法将其修改为没有重复整数的数组。小明会依次修改A2,A3,⋅⋅⋅,AN

当修改 Ai 时,小明会检查 Ai 是否在 A1 ~ Ai - 1中出现过。如果出现过,则小明会给 Ai 加上 1 ;如果新的 Ai 仍在之前出现过,小明会持续给Ai 加 1 ,直 到 Ai 没有在 A1 ~ Ai - 1中出现过。

AN 也经过上述修改之后,显然 A 数组中就没有重复的整数了。

现在给定初始的 A 数组,请你计算出最终的 A 数组。

输入:

第一行包含一个整数 N 。

第二行包含 N 个整数 A1,A2,⋅⋅⋅,AN。

其中,1 ≤ N ≤ 100000,1 ≤ Ai ≤ 1000000

输出:

输出 N 个整数,依次是最终的 A1,A2,⋅⋅⋅,AN。

输入输出样例:

输入

5
2 1 1 3 4

输出

2 1 3 4 

解题思路:

这道题目可以巧妙地运用并查集的思想来解决。核心在于维护一个记录已出现数字的集合,并针对每个新读入的数字,动态地更新这个集合。具体思路如下:

  1. 初始化并查集:初始化一个并查集,用于维护每个已出现数字的下一个未出现数字。

  2. 逐个处理数组中的元素

    • 如果当前数字之前没出现过,则它应该保持原值不变,且加入到集合中。此后,如果再次遇到这个数字,我们就知道需要输出这个数字加1。为此,我们把这个数字在并查集中的根节点设置为这个数字加1。
    • 如果当前数字之前已经出现过,则这个数字应变成其在并查集中的根节点的值。由于根节点的值代表着当前数字的下一个未出现的数字,因此直接使用根节点的值作为新值。同时,更新根节点的值为它自己加1,以便下次使用。
  3. 输出处理后的结果:每处理一个数字后,直接输出它经过处理后的值。不需要额外的数组来存储处理后的结果。

这种方法利用并查集有效地维护了一个动态更新的集合,对于每个新读入的数字,我们可以快速判断它是否已经出现过,并找到对应的新值。这个过程中,利用并查集的“合并”和“查找”操作,保证了算法的高效性。

// 并查集类
class UnionFind {
    private int[] parent; // 父节点数组

    // 构造函数,初始化并查集
    public UnionFind(int size) {
        parent = new int[size];
        for (int i = 0; i < size; i++) {
            parent[i] = i; // 初始时,每个元素的父节点是它自己
        }
    }

    // 查找操作:找到元素 x 的根节点
    public int find(int x) {
        if (x != parent[x]) {
            parent[x] = find(parent[x]); // 路径压缩,将x的父节点设置为其根节点
        }
        return parent[x];
    }

    // 合并操作:将元素 x 和 y 所在的集合合并
    public void union(int x, int y) {
        int rootX = find(x); // 找到x的根节点
        int rootY = find(y); // 找到y的根节点
        if (rootX != rootY) {
            parent[rootX] = rootY; // 将x的根节点的父节点设置为y的根节点
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt(); // 读入数组的长度
        UnionFind uf = new UnionFind(1000001); // 创建一个并查集,大小设为1000001

        for (int i = 0; i < n; i++) {
            int currentNum = scanner.nextInt(); // 读入当前数字
            int root = uf.find(currentNum); // 找到该数字在并查集中的根节点
            System.out.print(root + " "); // 输出该根节点(处理后的数字)

            if (root != 1000000) { // 如果根节点不是最大值
                uf.union(root, root + 1); // 将这个数字与它的下一个数字合并
            }
        }

        scanner.close();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1558507.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Topaz Photo AI for Mac v2.4.2 智能AI降噪软件

Topaz Photo AI是一款适用于Mac的图像处理软件&#xff0c;使用人工智能技术对照片进行编辑和优化。该软件提供了多种强大的功能&#xff0c;包括降噪、锐化、消除噪点、提高分辨率等&#xff0c;可以帮助用户改善图像质量&#xff0c;并实现自定义的效果。 软件下载&#xff1…

前端-html-02

1.列表 标签名功能和语义属性单标签还是双标签ul无序列表包裹元素双标签 ol 有序列表包裹元素双标签li列表项双标签dl定义列表包裹元素双标签dt定义列表项标题双标签dd定义列表项描述双标签 li必须由Ul或者ol包裹 <!DOCTYPE html> <html><head><…

Web APIs知识点讲解(阶段七)

正则表达式 1.能够利用正则表达式校验输入信息的合法性2. 具备利用正则表达式验证小兔鲜注册页面表单的能力 一.正则表达式 1.正则表达式 正则表达式&#xff08;Regular Expression&#xff09;是用于匹配字符串中字符组合的模式。在 JavaScript中&#xff0c;正则表达式也…

我们正在被 DDoS 攻击,但是我们啥也不干,随便攻击...

最近&#xff0c;一场激烈的攻防大战在网络世界悄然上演。 主角不是什么国家安全局或者黑客组织&#xff0c;而是一家名不见经传的创业公司——TablePlus。 DDoS 攻击者们摩拳擦掌&#xff0c;跃跃欲试。他们从四面八方蜂拥而至&#xff0c;誓要用数亿次请求把 TablePlus 的服…

Redis 常见数据结构及命令

目录 一.Redis常见的数据结构 二.Redis数据结构对应的命令 1.String类型 2.Hash类型 3.List类型 4.Set类型 5.Sorted Set类型 一.Redis常见的数据结构 Redis支持多种数据结构&#xff0c;包括字符串&#xff08;string&#xff09;、哈希&#xff08;hash&#xff09;、…

STM32的芯片无法在线调试的情况分析

问题描述 本博客的目的在于帮助网友尽快地解决问题&#xff0c; 避免浪费时间&#xff0c; 查漏补缺。 在stm32的开发过程中&#xff0c;有时会遇到"STM No Target connected"的错误提示&#xff0c;这说明MDK开发环境无法与目标设备进行通信&#xff0c;导致无法烧…

【JavaSE】类和对象详解(上)

欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 类和对象 类的组成 对类的理解 成员变量的访问和类方法的调用 this 抛出一个问题 this的作用 初始化成员变量 未初始化的成员变量 代码举例 就地初始化 构…

Autodesk Maya 2025 mac玛雅三维动画特效软件

Autodesk Maya 2025 for Mac是一款功能强大、操作简便的三维动画软件&#xff0c;适用于电影、电视、游戏、建筑、工业设计、虚拟现实和动画等领域。无论是专业设计师还是初学者&#xff0c;都可以通过Maya 2025实现自己的创意和想法&#xff0c;创作出高质量的三维作品。 软件…

浅谈Spring体系的理解

浅谈Spring知识体系 Spring Framework架构图Spring家族技术生态全景图XMind汇总 本文不涉及细节&#xff0c;主要回答两个问题&#xff1a; Spring家族技术生态全景图有哪些Spring Framework架构下每个模块有哪些东西&#xff0c;以及部分模块之间的关联关系 Spring Framework架…

探究贪心算法:特点与实际应用

探究贪心算法&#xff1a;特点与实际应用 探究贪心算法&#xff1a;特点与实际应用&#x1f4dd; 摘要&#x1f680; 引言&#x1f4cb; 正文内容&#xff08;详细介绍&#xff09;&#x1f4cc; 小结&#x1f4ca; 表格总结&#x1f3af; 总结&#x1f52e; 未来展望&#x1f…

【Redis教程0x08】详解Redis过期删除策略内存淘汰策略

引言 Redis的过期删除策略和内存淘汰策略是经常被问道的问题&#xff0c;这两个机制都是做删除操作&#xff0c;但是触发的条件和使用的策略是不同的。今天就来深入理解一下这两个策略。 过期删除策略 Redis 是可以对 key 设置过期时间的&#xff0c;因此需要有相应的机制将…

智能文档合规检测系统:在央企国企招标采购领域的应用

一、背景介绍 在央企国企采购过程中&#xff0c;合规性是一个不可忽视的重要方面。采购方需要确保供应商的资质、业绩、规模等条件符合采购要求&#xff0c;同时避免设置不合理的条件限制或排斥潜在供应商。为了提高采购效率和确保合规性&#xff0c;智能文档合规检测系统应运…

40.网络游戏逆向分析与漏洞攻防-角色管理功能通信分析-角色删除功能的数据包失败的分析

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果 内容参考于&#xff1a; 易道云信息技术研究院VIP课 上一个内容&#xff1a;39.角色数据的维…

Linux|centos7-postgresql数据库|yum安装数据库和配置repmgr高可用集群以及repmgr的日常管理工作

一、 前言 postgresql 的yum部署其实还是有点东西的&#xff0c;本文就做一个小小的记录&#xff0c;高可用方面repmgr插件还是非常不错的&#xff0c;但如何部署以及部署后如何使用也是一个难点&#xff0c;因此&#xff0c;也在本文里做一个记录 环境介绍&#xff1a; 第…

TonyBai go语言第一课 学习笔记

文章目录 大纲前置篇显式 大纲 前置篇 显式 package main import "fmt" func main() { var a int16 5 var b int 8 var c int64 c a b fmt.Printf("%d\n", c) }如果我们编译这段程序&#xff0c;将得到类似这样的编译器错误&#xff1a;“invalid op…

jmeter性能压测的标准和实战中会遇到的问题

1.性能标准建议 CPU 使用率&#xff1a;不超过 70% 内存使用率&#xff1a;不超过 70% 磁盘&#xff1a;%util到达80%严重繁忙 &#xff08;os.disIO.filesystem.writeKbPS 每秒写入的千字节&#xff09; 响应时间&#xff1a;95%的响应时间不超过8000ms 事务成功率&#xff1a…

计算线上点坐标

综上代码 void point_on_line(float* res, float* p, float* q, float lambda) {float tmp 1 - lambda;res[0] tmp* p[0] lambda * q[0];res[1] tmp* p[1] lambda * q[1];res[2] tmp* p[2] lambda * q[2]; }

基于SpringBoot华强北二手手机商城系统

点赞收藏关注 → 私信领取本源代码、数据库 一、项目概述 项目名称&#xff1a;基于SpringBoot华强北二手手机商城 项目架构&#xff1a;B/S架构 开发语言&#xff1a;Java语言 主要技术&#xff1a;SpringBootMybatisMySQL 运行环境&#xff1a;Windows7以上、JDK1.8、M…

【微服务】Nacos(配置中心)

文章目录 1.AP和CP1.基本介绍2.说明 2.Nacos配置中心实例1.架构图2.在Nacos Server加入配置1.配置列表&#xff0c;加号2.加入配置3.点击发布&#xff0c;然后返回4.还可以编辑 3. 创建 Nacos 配置客户端模块获取配置中心信息1.创建子模块 e-commerce-nacos-config-client50002…

【研发日记】Matlab/Simulink开箱报告(十一)——Requirements Toolbox

目录 前言 Requirements Toolbox 编写需求 需求联接设计 需求跟踪开发进度 追溯性矩阵 分析和应用 总结 前言 见《开箱报告&#xff0c;Simulink Toolbox库模块使用指南&#xff08;六&#xff09;——S-Fuction模块&#xff08;TLC&#xff09;》 见《开箱报告&#x…