当前位置: 首页 > news >正文

python 不相交集简介(并查集算法)【Introduction to Disjoint Set (Union-Find Algorithm)】

什么是不相交集数据结构?

        如果两个集合没有任何共同元素,则它们被称为不相交集,集合的交集为空集。

        存储不重叠或不相交元素子集的数据结构称为不相交集合数据结构。不相交集合数据结构支持以下操作:

1、将新集合添加到不相交集合中。

2、使用联合操作将不相交集合并为单个不相交集。

3、使用“查找”操作查找不相交集的代表。

4、检查两个集合是否不相交。 

考虑这样一个情况,有许多人需要执行以下任务:

        1、添加新的友谊关系,即一个人 x 成为另一个人 y 的朋友,即向集合中添加新元素。

        2、判断个体x 是否是个体 y 的朋友(直接或间接朋友)

例子: 


我们有 10 个人,比如 a、b、c、d、e、f、g、h、i、j


以下是需要添加的关系:
a <-> b   
b <-> d 
c <-> f 
c <-> i 
j <-> e 
g <-> j


给定查询,例如 a 是否是 d 的朋友。我们基本上需要创建以下 4 个组,并在组项之间保持快速访问的连接:
G1 = {a, b, d} 
G2 = {c, f, i} 
G3 = {e, g, j} 
G4 = {h}


判断 x 和 y 是否属于同一组,即判断 x 和 y 是否是直接/间接朋友。

        根据个体所属的组别,将个体划分为不同的集合。此方法称为不相交集合并集,它维护不相交集合的集合,每个集合由其成员之一表示。

要回答上述问题,需要考虑两个关键点:

        1、如何解析集合?最初,所有元素都属于不同的集合。在处理给定的关系后,我们选择一个成员作为代表。选择代表的方法有很多种,一种简单的方法是选择最大的索引。

        2、检查两个人是否在同一组中?如果两个人的代表相同,那么他们就会成为朋友。

使用的数据结构包括: 

        数组:整数数组称为Parent[]。如果我们处理N 个项目,则数组的第 i 个元素代表第 i 个项目。更准确地说,Parent[] 数组的第 i 个元素是第 i 个项目的父级。这些关系创建一个或多个虚拟树。

        树:它是一个不相交集。如果两个元素在同一棵树中,那么它们就在同一个不相交集。每棵树的根节点(或最顶端节点)称为集合的代表。每个集合始终有一个唯一的代表。识别代表的一个简单规则是,如果“i”是集合的代表,则Parent[i] = i。如果 i 不是其集合的代表,则可以通过沿树向上移动直到找到代表来找到它。

不相交集合数据结构上的操作:
        查找
        联合

1. 查找:
        可以通过递归遍历父数组直到找到其自身的父节点来实现。

# Finds the representative of the set 
# that i is an element of 
  
def find(i): 
  
    # If i is the parent of itself 
    if (parent[i] == i): 
  
        # Then i is the representative of 
        # this set 
        return i 
    else: 
  
        # Else if i is not the parent of 
        # itself, then i is not the 
        # representative of his set. So we 
        # recursively call Find on its parent 
        return find(parent[i]) 
  
 # The code is contributed by Nidhi goel 

时间复杂度:这种方法效率低下,在最坏的情况下可能需要 O(n) 时间。

2. 联合: 

        它以两个元素作为输入,并使用查找操作找到它们的集合的代表,最后将其中一棵树(代表集合)放在另一棵树的根节点下。

# Unites the set that includes i 
# and the set that includes j 
  
def union(parent, rank, i, j): 
    # Find the representatives 
    # (or the root nodes) for the set 
    # that includes i 
    irep = find(parent, i) 
      
    # And do the same for the set 
    # that includes j 
    jrep = find(parent, j) 
      
    # Make the parent of i’s representative 
    # be j’s  representative effectively 
    # moving all of i’s set into j’s set) 
      
    parent[irep] = jrep  

时间复杂度:这种方法效率低下,在最坏的情况下可能导致长度为 O(n)的树。

优化(按等级/大小合并和路径压缩):

    效率在很大程度上取决于哪棵树连接到另一棵树。有两种方法可以实现。第一种是按等级联合,它将树的高度视为一个因素;第二种是按大小联合,它将树的大小视为一个因素,同时将一棵树连接到另一棵树。此方法与路径压缩一起提供了几乎恒定时间的复杂性。

路径压缩(对 Find() 的修改):

    它通过压缩树的高度来加速数据结构。这可以通过在Find操作中插入一个小的缓存机制来实现。查看代码了解更多详细信息:

#  Finds the representative of the set that i 
# is an element of. 
  
  
def find(i): 
  
    # If i is the parent of itself 
    if Parent[i] == i: 
  
        # Then i is the representative  
        return i 
    else: 
  
        # Recursively find the representative. 
        result = find(Parent[i]) 
  
        # We cache the result by moving i’s node  
        # directly under the representative of this 
        # set 
        Parent[i] = result 
        
        # And then we return the result 
        return result 
  
# The code is contributed by Arushi  Jindal.   

时间复杂度:平均每次调用为 O(log n)。

按等级合并:

    首先,我们需要一个新的整数数组,名为rank[] 。此数组的大小与父数组Parent[]相同。如果 i 代表一个集合,则rank[i]就是代表该集合的树的高度。 现在回想一下,在 Union 操作中,将两棵树中的哪一棵移动到另一棵之下并不重要。现在我们要做的是最小化结果树的高度。如果我们要合并两棵树(或集合),我们将它们称为左和右,那么这一切都取决于左的等级和右的等级。 

        1、如果左边的等级小于右边的等级,那么最好将左边移到右边的下方,因为这不会改变右边的等级(而将右边移到左边的下方会增加高度)。同样,如果右边的等级小于左边的等级,那么我们应该将右边移到左边的下方。

        2、如果等级相等,那么哪棵树位于另一棵树之下并不重要,但结果的等级始终比树的等级大一。

class DisjointSet: 
    def __init__(self, size): 
        self.parent = [i for i in range(size)] 
        self.rank = [0] * size 
  
    # Function to find the representative (or the root node) of a set 
    def find(self, i): 
        # If i is not the representative of its set, recursively find the representative 
        if self.parent[i] != i: 
            self.parent[i] = self.find(self.parent[i])  # Path compression 
        return self.parent[i] 
  
    # Unites the set that includes i and the set that includes j by rank 
    def union_by_rank(self, i, j): 
        # Find the representatives (or the root nodes) for the set that includes i and j 
        irep = self.find(i) 
        jrep = self.find(j) 
  
        # Elements are in the same set, no need to unite anything 
        if irep == jrep: 
            return
  
        # Get the rank of i's tree 
        irank = self.rank[irep] 
  
        # Get the rank of j's tree 
        jrank = self.rank[jrep] 
  
        # If i's rank is less than j's rank 
        if irank < jrank: 
            # Move i under j 
            self.parent[irep] = jrep 
        # Else if j's rank is less than i's rank 
        elif jrank < irank: 
            # Move j under i 
            self.parent[jrep] = irep 
        # Else if their ranks are the same 
        else: 
            # Move i under j (doesn't matter which one goes where) 
            self.parent[irep] = jrep 
            # Increment the result tree's rank by 1 
            self.rank[jrep] += 1
  
    def main(self): 
        # Example usage 
        size = 5
        ds = DisjointSet(size) 
  
        # Perform some union operations 
        ds.union_by_rank(0, 1) 
        ds.union_by_rank(2, 3) 
        ds.union_by_rank(1, 3) 
  
        # Find the representative of each element 
        for i in range(size): 
            print(f"Element {i} belongs to the set with representative {ds.find(i)}") 
  
  
# Creating an instance and calling the main method 
ds = DisjointSet(size=5) 
ds.main() 

按大小合并:

    同样,我们需要一个新的整数数组,名为size[] 。此数组的大小与父数组Parent[]相同。如果 i 代表一个集合,则size[i]是代表该集合的树中元素的数量。 现在我们将两棵树(或集合)合并起来,我们将它们称为左树和右树,在这种情况下,一切都取决于左树(或集合)的大小和右树(或集合)的大小。

        1、如果左边的尺寸小于右边的尺寸,那么最好将左边移到右边下方,并将右边的尺寸增加左边的尺寸。同样,如果右边的尺寸小于左边的尺寸,那么我们应该将右边移到左边下方,并将左边的尺寸增加右边的尺寸。

        2、如果尺寸相等,那么哪棵树位于另一棵树下都没有关系。

# Python program for the above approach 
class UnionFind: 
    def __init__(self, n): 
        # Initialize Parent array 
        self.Parent = list(range(n)) 
  
        # Initialize Size array with 1s 
        self.Size = [1] * n 
  
    # Function to find the representative (or the root node) for the set that includes i 
    def find(self, i): 
        if self.Parent[i] != i: 
            # Path compression: Make the parent of i the root of the set 
            self.Parent[i] = self.find(self.Parent[i]) 
        return self.Parent[i] 
  
    # Unites the set that includes i and the set that includes j by size 
    def unionBySize(self, i, j): 
        # Find the representatives (or the root nodes) for the set that includes i 
        irep = self.find(i) 
  
        # And do the same for the set that includes j 
        jrep = self.find(j) 
  
        # Elements are in the same set, no need to unite anything. 
        if irep == jrep: 
            return
  
        # Get the size of i’s tree 
        isize = self.Size[irep] 
  
        # Get the size of j’s tree 
        jsize = self.Size[jrep] 
  
        # If i’s size is less than j’s size 
        if isize < jsize: 
            # Then move i under j 
            self.Parent[irep] = jrep 
  
            # Increment j's size by i's size 
            self.Size[jrep] += self.Size[irep] 
        # Else if j’s size is less than i’s size 
        else: 
            # Then move j under i 
            self.Parent[jrep] = irep 
  
            # Increment i's size by j's size 
            self.Size[irep] += self.Size[jrep] 
  
# Example usage 
n = 5
unionFind = UnionFind(n) 
  
# Perform union operations 
unionFind.unionBySize(0, 1) 
unionFind.unionBySize(2, 3) 
unionFind.unionBySize(0, 4) 
  
# Print the representative of each element after unions 
for i in range(n): 
    print("Element {}: Representative = {}".format(i, unionFind.find(i))) 
  
# This code is contributed by Susobhan Akhuli

输出
元素 0:代表 = 0
元素 1:代表 = 0
元素 2:代表 = 2
元素 3:代表 = 2
元素 4:代表 = 0


时间复杂度:O(log n),无路径压缩。

下面是具有路径压缩和按等级合并的不相交集的完整实现。

# Python3 program to implement Disjoint Set Data 
# Structure. 
  
class DisjSet: 
    def __init__(self, n): 
        # Constructor to create and 
        # initialize sets of n items 
        self.rank = [1] * n 
        self.parent = [i for i in range(n)] 
  
  
    # Finds set of given item x 
    def find(self, x): 
          
        # Finds the representative of the set 
        # that x is an element of 
        if (self.parent[x] != x): 
              
            # if x is not the parent of itself 
            # Then x is not the representative of 
            # its set, 
            self.parent[x] = self.find(self.parent[x]) 
              
            # so we recursively call Find on its parent 
            # and move i's node directly under the 
            # representative of this set 
  
        return self.parent[x] 
  
  
    # Do union of two sets represented 
    # by x and y. 
    def Union(self, x, y): 
          
        # Find current sets of x and y 
        xset = self.find(x) 
        yset = self.find(y) 
  
        # If they are already in same set 
        if xset == yset: 
            return
  
        # Put smaller ranked item under 
        # bigger ranked item if ranks are 
        # different 
        if self.rank[xset] < self.rank[yset]: 
            self.parent[xset] = yset 
  
        elif self.rank[xset] > self.rank[yset]: 
            self.parent[yset] = xset 
  
        # If ranks are same, then move y under 
        # x (doesn't matter which one goes where) 
        # and increment rank of x's tree 
        else: 
            self.parent[yset] = xset 
            self.rank[xset] = self.rank[xset] + 1
  
# Driver code 
obj = DisjSet(5) 
obj.Union(0, 2) 
obj.Union(4, 2) 
obj.Union(3, 1) 
if obj.find(4) == obj.find(0): 
    print('Yes') 
else: 
    print('No') 
if obj.find(1) == obj.find(0): 
    print('Yes') 
else: 
    print('No') 
  
# This code is contributed by ng24_7. 

 输出

Yes
No

时间复杂度:创建 n 个单项集的时间为 O(n)。两种技术(路径压缩和按等级/大小合并)的时间复杂度将达到接近常数时间。事实证明,最终的 摊销时间复杂度为 O(α(n)),其中 α(n) 是逆阿克曼函数,其增长非常稳定(当 n<10 600   时,它甚至不会超过)。

空间复杂度: O(n),因为我们需要在不相交集数据结构中存储 n 个元素。

相关文章:

python 不相交集简介(并查集算法)【Introduction to Disjoint Set (Union-Find Algorithm)】

什么是不相交集数据结构&#xff1f; 如果两个集合没有任何共同元素&#xff0c;则它们被称为不相交集&#xff0c;集合的交集为空集。 存储不重叠或不相交元素子集的数据结构称为不相交集合数据结构。不相交集合数据结构支持以下操作&#xff1a; 1、将新集合添加到不相交集合…...

23种设计模式之工厂方法模式

文章目录 1. 简介2. 代码2.1 抽象类&#xff1a;Course.java2.2 产品A&#xff1a;JavaCourse.java2.3 产品B&#xff1a;PythonCourse.java2.4 工厂抽象类&#xff1a;CourseFactory.java2.5 产品A的工厂A&#xff1a;JavaCourseFactory.java2.6 产品B的工厂B&#xff1a;PyCo…...

Redis——事务

文章目录 Redis 事务Redis 的事务和 MySQL 事务的区别:事务操作MULTIEXECDISCARDWATCHUNWATCHwatch的实现原理 总结 Redis 事务 什么是事务 Redis 的事务和 MySQL 的事务 概念上是类似的. 都是把⼀系列操作绑定成⼀组. 让这⼀组能够批量执行 Redis 的事务和 MySQL 事务的区别:…...

Redis非关系型数据库操作命令大全

以下是 Redis 的常用操作命令大全&#xff0c;涵盖了键值操作、字符串、哈希、列表、集合、有序集合、发布/订阅、事务等多个方面的操作。 1. 通用键命令 命令说明SET key value设置指定 key 的值GET key获取指定 key 的值DEL key删除指定的 keyEXISTS key检查 key 是否存在E…...

基于SpringBoot+Vue+uniapp微信小程序的澡堂预订的微信小程序的详细设计和实现

项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不是配置文件。Spring Boot 通过自动化配置和约…...

Linux mips架构链接库函数调用plt表汇编代码分析

linux调用共享库中的函数时通过plt表和got表实现位置无关代码&#xff0c;过程中涉及到lazy binding&#xff0c;即在第一调用外部函数时解析被调用的函数地址并将地址写入到got表&#xff0c;后续调用则不需要解析函数地址。这一部分和硬件架构有关&#xff0c;具体的是和cpu指…...

python 作业1

任务1: python为主的工作是很少的 学习的python的优势在于制作工具&#xff0c;制作合适的工具可以提高我们在工作中的工作效率的工具 提高我们的竞争优势。 任务2: 不换行 换行 任务3: 安装pycharm 进入相应网站Download PyCharm: The Python IDE for data science and we…...

Apache 出现 “403 forbidden“ 排查方法

1、检查运行 Apache 进程的用户没有对目录具备读取权限 如果该用户没有对 Directory 指定的目录具备适当的读取权限&#xff0c;就会导致 403 错误。 ​​例如&#xff1a;使用用户apache启动Apache进程&#xff0c;但是apache用户对 Directory 指定的目录没有读取权限 2、检查…...

vue video播放m3u8监控视频

很关键的问题 vite创建的项目不需要import ‘videojs-contrib-hls’ 导入就报错 直接添加如下代码即可 html5: {vhs: {overrideNative: true},nativeVideoTracks: false,nativeAudioTracks: false,nativeTextTracks: false} 下面是完整组件示例 <template><div>…...

uniapp 获取签名证书 SHA1 自有证书签名打包

1.登录你的Dcloud 账户 2.找到我的应用菜单 3.点开某个应用 4.查看证书详情&#xff0c;里面有SHA1 和别名&#xff0c;密码&#xff0c;下载证书用于云打包&#xff0c;可以选择自有证书&#xff0c;输入别名&#xff0c;密码打包...

Open3d开发点云标注工具问题总结(二)

前面我们介绍了使用AABB方式来框选点云&#xff0c;但这种方式还是不够直观&#xff0c;我们的构想是设计一个和o3d.visualization.VisualizerWithEditing的点云框选方法一样的软件&#xff0c;因此&#xff0c;博主想到利用投影的形式进行解决&#xff1a; 具体的&#xff0c;…...

【FreeRTOS】

报错&#xff1a; 使用STM32cubemx自动生成freertos选项V2报错&#xff0c;V1不报错 …/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/freertos_os2.h(31): 解决 修改cubemx配置&#xff0c;将V1.8.6改选为V1.8.5后编译不再报错...

洛谷 P4995:跳跳! ← 贪心算法

【题目来源】https://www.luogu.com.cn/problem/P4995【题目描述】你是一只小跳蛙&#xff0c;你特别擅长在各种地方跳来跳去。 这一天&#xff0c;你和朋友小 F 一起出去玩耍的时候&#xff0c;遇到了一堆高矮不同的石头&#xff0c;其中第 i 块的石头高度为 hi&#xff0c;地…...

代理 IP 在 AI 爬虫中的关键应用

现如今&#xff0c;人工智能&#xff08;AI&#xff09;的发展日新月异&#xff0c;而数据作为驱动 AI 发展的关键要素&#xff0c;其重要性不言而喻。AI 爬虫作为获取大量数据的重要工具&#xff0c;在数据收集过程中发挥着至关重要的作用。而代理 IP 在 AI 爬虫中有着广泛而重…...

【Vercel】Vercel静态部署踩坑

背景 在现代的软件开发中&#xff0c;自动化部署是一个不可或缺的环节。Vercel作为一个流行的前端部署平台&#xff0c;提供了与GitHub的无缝集成&#xff0c;使得开发者能够在每次提交代码后自动触发部署流程。然而&#xff0c;自动化部署过程中可能会遇到一些挑战&#xff0…...

【Spring】关于Spring中aware相关接口的作用

Aware 接口的回调方法是在 Bean 实例化之后调用的。具体来说&#xff0c;这些回调方法是在依赖注入完成后&#xff0c;但在 Bean 完全初始化之前调用的。这是 Spring 容器管理 Bean 生命周期的一部分 完成了属性赋值之后&#xff0c;Spring会执行一些回调&#xff0c;包括&…...

动态内存管理及RAII的简单应用

目录 一.程序启动所关联的内存分区 二.动态内存的申请和释放 三.将RAII思想融入代码 四.RAII思想的简单应用 一.程序启动所关联的内存分区 .dll文件是Dynamic Link Library&#xff08;动态链接库&#xff09;文件的缩写&#xff0c;它是一种共享库文件&#xff0c;包含…...

7、Vue2(一)

1.认识Vue 官网地址&#xff1a;https://v2.cn.vuejs.org/v2/guide/ Vue.js 是一套构建用户界面的渐进式框架。 Vue 2 是在2016年发布使用&#xff0c;2020是 vue3 才刚发布&#xff0c;时隔一年左右就已经将 vue3 作为了默认版本 尤雨溪&#xff0c;Vue.js和Vite的作者&…...

Chapter11

11.3 #include <stdio.h> #include <string.h> #define NUM_STUDENTS 40 #define NUM_SUBJECTS 3 // 学生结构体 typedef struct { int id; char name[50]; float scores[NUM_SUBJECTS]; float average; } Student; void inputData(Student studen…...

LLAMA2入门(一)-----预训练

Llama 2 是预训练和微调的LLM系列&#xff0c;Llama 2 和 Llama 2-Chat 模型的参数规模达到 70B。Llama 2-Chat 模型专门为对话场景进行了优化。 这是一个系列的文章&#xff0c;会分别从LLAMA2的预训练&#xff0c;微调&#xff0c;安全性等方面进行讲解。 1.数据来源 数据…...

Cursor实现用excel数据填充word模版的方法

cursor主页&#xff1a;https://www.cursor.com/ 任务目标&#xff1a;把excel格式的数据里的单元格&#xff0c;按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例&#xff0c;…...

C++初阶-list的底层

目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

Xshell远程连接Kali(默认 | 私钥)Note版

前言:xshell远程连接&#xff0c;私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

FFmpeg 低延迟同屏方案

引言 在实时互动需求激增的当下&#xff0c;无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作&#xff0c;还是游戏直播的画面实时传输&#xff0c;低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架&#xff0c;凭借其灵活的编解码、数据…...

LeetCode - 394. 字符串解码

题目 394. 字符串解码 - 力扣&#xff08;LeetCode&#xff09; 思路 使用两个栈&#xff1a;一个存储重复次数&#xff0c;一个存储字符串 遍历输入字符串&#xff1a; 数字处理&#xff1a;遇到数字时&#xff0c;累积计算重复次数左括号处理&#xff1a;保存当前状态&a…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

leetcodeSQL解题:3564. 季节性销售分析

leetcodeSQL解题&#xff1a;3564. 季节性销售分析 题目&#xff1a; 表&#xff1a;sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...