算法导论 总结索引 | 第五部分 第二十一章:用于不相交集合的数据结构

news2024/9/23 23:26:51

一些应用涉及 将n个不同的元素分成一组不相交的集合。寻找包含给定元素的唯一集合 和 合并两个集合

1、不相交集合的操作

1、一个不相交集合 数据结构 维持了 一个不相交动态集的集合 S = {S_1, S_2,…, S_n}。用一个代表 来标识每个集合,它是这个集合的某个成员。在一些应用中,不关心 哪个成员被用来作为代表,仅仅关心的是 重复两次查询动态集合的代表中,如果 这两次查询 没有修改动态集合,则两次查询 应该得到相同的结果。其他一些应用 可能会需要一个预先说好的规则 来选择代表,比如 选择这个集合中最小的成员

2、用一个对象 表示一个集合的每个元素。设 x 表示一个对象,希望支持以下三个操作:

  1. MAKE-SET(x): 建立 一个新的集合,它的唯一成员 (因而为代表) 是 x。因为各个集合是不相交的,故 x 不会出现在 别的某个集合中
  2. UNION(x, y): 将包含 x 和 y 的两个动态集合 (分别表示为S_x和S_y) 合成一个新的集合,即这两个集合的并集。虽然 UNION 的很多实现中 特别地选择 S_x 或 S_y 的代表作为新的代表,然而结果集的代表 可以是S_x∪S_y 的任何成员。由于 要求各个集合不相交,故要 “消除” 原有的集合 S_x 和 S_y,即 将它们从 S 中删除。实际上,经常把 其中一个集合的元素 并入另一个集合中,来代替删除操作
  3. FIND-SET(x): 返回一个指针,这个指针 指向包含 x 的(唯一)集合的代表

使用两个参数 来分析 不相交集合数据结构的运行时间: 一个参数是 n,表示 MAKES-SET 操作的次数;另一个是m,表示 MAKE-SET、UNION 和 FIND-SET 操作的总次数

每个 UNION 操作减少一个集合,因此,n - 1 次 UNION 操作后,只有一个集合留了下来。也就是说,UNION 操作的次数 至多是 n - 1。由于 MAKE-SET 操作 被包含在总操作次数 m 中,因此有 m ≥ n。假定 n 个 MAKE-SET 操作总是最先执行

1.1 不相交集合数据结构的一个应用

确定无向图的连通分量

下面的 CONNECTED-COMPONENTS 过程使用 不相交集合操作 计算一个图的连通分量。一旦 SAME-COMPONENTS 预处理了该图,过程 SAME-COMPONENT 就回答两个顶点是否在 同一个连通分量的询问(图G的顶点集用 G.V 表示,边集用 G.E 表示)

图21-1(b)展示了 CONNECTED-COMPONENTS 如何计算不相交集合
在这里插入图片描述

CONNECTED-COMPONENTS(G)
1 for each vertex v ∈ G.V
2 	MAKE-SET(v)
3 for each edge (u, v) ∈ G.E
4 	if FIND-SET(u) ≠ FIND-SET(v)
5 		UNION(u, v)

SAME-COMPONENT(u,v)
1 if FIND-SET(u) == FIND-SET(v)
2 	return TRUE
3 else return FALSE

处理完所有的边之后,两个顶点在相同的连通分量 当且仅当 与之对应的对象在相同的集合中
一个表示顶点的对象会包含一个指向与之对应的不相交集合对象的指针

在这里插入图片描述

2、不相交集合的链表表示

实现 不相交集合数据结构 的简单方法:每个集合 用一个自己的链表来表示。每个集合的对象 包含 head 属性和 tail 属性;head 属性 指向链表的第一个对象,tail 属性 指向链表的最后一个对象。链表中的每个对象 都包含一个集合成员,一个指向链表中 下一个对象的指针 和 一个指回到集合对象的指针。在每个链表中,对象可以 以任意的次序出现。代表是 链表中第一个对象的集合成员

MAKE-SET 操作 和 FIND-SET 操作 是非常方便的,只需 O(1) 的时间

要执行 MAKE-SET(x) 操作,需要创建一个 只有 x 对象的新链表。对于 FIND-SET(x),仅沿着指针 x 对象的返回指针 返回到集合对象,然后返回 head 指向对象的成员
在这里插入图片描述

2.1 合并的一个简单实现

UNION 操作 通过把 y 所在的链表 拼接到 x 所在的链表 实现了 UNION (x, y)。x 所在的链表的代表 成为结果集的代表。利用 x 所在链表的 tail 指针,可以迅速地找到 拼接 y 所在的链表的位置

对于 y 所在链表的每个对象,必须更新 指向集合对象的指针,这将花费的时间 与 y 所在链表长度 呈线性关系

构建一个在 n 个对象上需要 Θ(n2) 时间的 m 个操作序列。假设 有对象 x1, x2, …, xm,执行 n 个 MAKE-SET 操作,后面跟着 n-1 个 UNION 操作。因而有 m = 2n - 1。执行 n 个 MAKE-SET 操作 需要 Θ(n) 时间。由于第 i 个 UNION 操作更新 i 个对象(参数中 右侧 插在 左侧的集合后面)
在这里插入图片描述

因此所有的 n-1 个 UNION 操作更新的对象的总数为:
在这里插入图片描述
总的操作数为 2n-1,这样每个操作 平均需要 Θ(n) 的时间。也就是说,一个操作的摊还时间为 Θ(n)

2.2 一种加权合并的启发式策略

1、在最坏情况下,上面给出的 UNION 过程的每次调用 平均需要 Θ(n) 的时间,这是因为 需要把一个较长的表 接到 一个较短的表上,此时 必须对较长表的每个成员 更新其指向集合对象的指针

加权合并启发式策略:假使 表中包含了 表的长度(易于维护)以及 拼接次序 可以任意的话,总是 把较短的链表 拼接到较长的表中

2、使用不相交集合的链表表示 加权合并启发式策略,一个具有 m 个 MAKE-SET, UNION 和 FIND-SET 操作的序列(其中有 n 个是 MAKE-SET 操作)需要的时间为 O(m + nlg n)

证明:由于每个UNION操作 合并两个不相交集,因此总共至多执行 n-1 个UNION操作。现在来确定 由这些 UNION 操作所花费时间的上界。首先 确定每个对象指向它的集合对象的指针 被更新次数的上界。每次 x 的指针被更新,x 一定先在一个规模较小的集合中。因此,第一次 x 的指针被更新时,结果集 一定至少有 2 个成员,类似地,下次 x 的指针 被更新时 结果集至少有4个成员。一直继续下去,注意到 对于任意的 k ≤ n,在 x 的指针被更新 ⌈lg k⌉ 次后,结果集 一定至少有 k 个成员。因为 最大集合 至多包含 n 个成员,所以 每个对象的指针在所有的 UNION 操作中最多被更新 ⌈lg n⌉ 次。当然,也必须考虑 tail 指针 和 表长度的更新,而它们在每个 UNION 操作中只花费 Θ(1) 时间。所以 总共花在 UNION 操作上的时间为 O(nlg n)

每个 MAKE-SET 和 FIND-SET 操作需要 O(1) 时间,它们的总数为 O(m)。所以整个序列的总时间是 O(m+nlg n)

3、使用链表表示 和 加权合并启发式策略,写出 MAKE-SET、FIND-SET 和 UNION 操作的伪代码,并指定 在集合对象和表对象中所使用的属性

MAKE-SET(x)
// Assume x is a pointer to a node contains .key .set .next
    Create a node S contains .head .tail .size
    x.set = S
    x.next = NIL
    S.head = x
    S.tail = x
    S.size = 1
    return S
FIND-SET(x)
    return x.set.head
UNION(x, y)
    S1 = x.set
    S2 = y.set
    if S1.size >= S2.size
        S1.tail.next = S2.head
        z = S2.head
        while z != NIL // 把S2中所有元素都改成S1的
            z.set = S1
            z = z.next
        S1.tail = S2.tail
        S1.size = S1.size + S2.size // Update the size of set
        return S1
    else
        same procedure as above
        change x to y
        change S1 to S2

在这里插入图片描述

3、不相交集合森林

在一个 不相交集合 更快的实现中,使用有根树 来表示集合,树中的每个节点 包含一个成员,每棵树 代表一个集合。在一个 不相交集合森林中(如图所示),每个成员仅指向它的父结点,每棵树的根节点 包含集合的代表。每个树中的每个成员都是它所在集合的成员,同时其根节点表示该集合的代表,并且是指向代表的根节点。我们可以在树根节点中做任何操作来执行FIND-SET,虽然树根节点可能处于集合森林的不同位置,但是结果总是同样的成员代表。随着进一步的优化和策略“按秩合并”(union by rank),我们能得到一个渐近更优的不相交集合数据结构

MAKE-SET 操作 简单地创建一棵 只有一个结点的树,FIND-SET 操作 通过沿着指向父结点的指针 找到树的根。这一通向 根结点的简单路径上所访问的结点 构成了 查找路径。UNION 操作 使得一棵树的根 指向另外一棵树的根
在这里插入图片描述

3.1 改进运行时间的启发式策略

第一种启发式策略是 按秩合并,它类似于 链表表示中 使用的 加权合并启发式策略。使具有较少节点的树的根 指向具有较多的树的根。不显式地记录 每个结点为根的子树的大小,而是采用 一种易于分析的方法。对于每个结点,维护一个秩,该秩表示 该结点高度的一上界。使用按秩合并策略的 UNION 操作中,可以让 具有较小秩的根指向具有较大秩的根

第二种启发式策略是 路径压缩,在 FIND-SET 操作中,使用这种策略 可以使查找路径中的每个结点 直接指向根。路径压缩 并不改变任何结点的秩

注意三角形是一棵树,而不是 结点
在这里插入图片描述

3.2 实现不相交集合森林的伪代码

为了使用 按秩合并的启发式策略 实现一个不相交集合森林,必须记录下 的变化情况。对于每个结点 x,维护一个整数值 x.rank,它代表 x 的高度 (从 x 到某一后代叶结点的 最长简单路径上 边的数量) 的一个上界。当 MAKE-SET 创建 一个单元素集合时,这个树上的单结点 有一个为0的初始秩。每一个 FIND-SET 操作 不改变任何秩

UNION 操作有 两种情况,取决于 两棵树的根是否有相同的秩。如果 根没有相同的秩,就 让较大秩的根 成为较小秩的根的父结点(因为 FIND-SET 复杂度是高度),但秩本身保持不变。另一种情况是 两个根有相同的秩时,任意选择两个根中的一个 作为父结点,并使它的秩加1

用 x.p 代表结点 x 的父结点

MAKESET(x)
1	x.p = x
2	x.rank = 0

UNION(x, y)
1   LINK(FIND-SET(x), FIND-SET(y))

LINK(x, y)
1   if x.rank > y.rank
2      y.p = x
3   else x.p = y
4      if x.rank == y.rank
5         y.rank = y.rank + 1

带有路径压缩的 FIND-SET 过程

FIND-SET(x)
1   if x != x.p
2      x.p = FIND-SET(x.p) // 使 x 到根路径上的所有结点的父结点 为根结点
3   return x.p

FIND-SET 过程是一种两趟方法:当它递归时,第一趟 沿着查找路径向上 直到找到根,当递归回溯时,第二趟沿着搜索树向下 更新到 结点 x 路径中的每个结点,使其直接指向根。FIND-SET(x) 的每次调用 在第3行返回 x.p

如果 x 是根,那么 FIND-SET 跳过第2行并返回 x.p,也就是x,这是递归到原点的情形。否则,第2行执行,并且参数为 x.p 的递归调用 返回一个指向根的指针。第2行 更新结点 x 并让其直接指向根结点,然后第3行 返回这个指针

3.3 启发式策略对运行时间的影响

单独使用按秩合并 或 路径压缩,每个都能改善 不相交集合森林上 操作的运行时间,而两者结合在一起 效果更好。单独来看,路径压缩产生的运行时间上限为 O(m lg n),并且这是个界是紧确的

对于一个具有 n 个 MAKE-SET 操作(因此最多有 n-1 个UNION操作)和 f 个 FIND-SET 操作的操作序列,单独使用 路径压缩启发式策略的 最坏情况下的 运行时间为 Θ(n+ f*(1 + log(2+f/n)n))

当同时使用 按秩合并与路径压缩时,最坏情况下的运行时间为 O(mα(n)),这里 α(n) 是一个增长非常慢的函数,在任何一个 可以想到的 不相交集合数据结构的应用中,α(n) 都 ≤ 4

21.3-1 用按秩合并 与 路径压缩启发式策略 的不相交集合森林 完成

1 for i = 1 to 16
2 	MAKE-SET(x_i)
3 for i = 1 to 15 by 2
4 	UNION(x_i, x_{i+1})
5 for i = 1 to 13 by 4
6 	UNION(x_i, x_{i+2})
7 UNION(x_1, x_5)
8 UNION(x_9, x_13)
9 UNION(x_1, x_9)

假定如果集合x_i 和x_j 的集合有相同的大小,则 UNION(x_i, x_j) 表示将x_j 所在的表链接到x_i 所在的表后
在这里插入图片描述
21.3-2 将递归版本的 FIND-SET(带路径压缩)改为非递归版本。

为了实现非递归的 FIND-SET,假设我们在元素 x 上调用该函数。创建一个链表 A,其中包含指向 x 的指针。每次我们 向树的上层移动一个元素时,将指向该元素的指针 插入链表 A 中。一旦找到根节点 r,使用该链表找到从根节点到 x 的路径上的每个节点,并将这些节点的父节点更新为 r

21.3-3 给出一个包含n个 MAKE-SET、UNION 和 FIND-SET 操作的序列(其中有 n 个是 MAKE-SET 操作),当仅使用按秩合并时,需要 Ω(m lg n) 的时间
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

IoTDB 入门教程 企业篇④——安全控制 | 白名单、审计日志、登录日志和操作日志

文章目录 一、前文二、白名单2.1 配置文件iotdb-common.properties2.2 配置文件white.list2.3 注意事项 三、审计日志3.1 Cli操作日志3.2 RESTful操作日志3.3 MQTT操作日志3.4 Java操作日志3.5 C#操作日志3.6 Python操作日志 四、参考 一、前文 IoTDB入门教程——导读 IoTDB企业…

C语言9~10 DAY(合集)

数组的概念 什么是数组 数组是相同类型,有序数据的集合。 数组的特征 数组中的数据被称为数组的元素,是同构的 数组中的元素存放在内存空间里 (char player_name[6]:申请在内存中开辟6块连续的基于char类型的变量空间) 衍生概念&#x…

力扣高频SQL 50题(基础版)第三十七题

文章目录 力扣高频SQL 50题(基础版)第三十七题176.第二高的薪水题目说明实现过程准备数据实现方式结果截图总结 力扣高频SQL 50题(基础版)第三十七题 176.第二高的薪水 题目说明 Employee 表: ----------------- …

【传知代码】基于标签相关性的多标签学习(论文复现)

在当今信息爆炸的时代,数据中包含的标签信息对于理解和分析复杂问题至关重要。在诸如文本分类、图像识别和推荐系统等应用中,如何有效地利用标签相关性提升多标签学习的效果成为了研究的热点之一。基于标签相关性的多标签学习方法,通过挖掘不…

存储届的奥运竞技 | 400层3D NAND最快2025到来~

随着内存巨头之间的高带宽内存 (HBM) 竞争日益激烈,NAND 存储器领域的竞争也在升温。据韩国媒体《etnews》报道,SK 海力士正在研发 400 层 NAND 闪存技术,计划在 2025 年底前准备好这项技术以实现量产。 报道称,SK 海力士目前正在…

AcWing并查集

建议先看这个 Bilibili------------------>图论——并查集(详细版) 其实M 1 2就是把1的祖先改成了2,然后M 3 4就是把3的祖先改成了4,然后查询这两数1,2的祖先是不是同一个,3,4的祖先是不是同一个,1,3的祖先是不是同…

【期货】收盘点评。昨天说的,p2409棕榈油在今天或者周一会走出行情

收盘点评 昨天说的,p2409棕榈油在今天或者周一会走出行情。事实就是如此。震荡了几天了,波幅不大的来回震荡,其实主力是不想震荡的,但是不震荡自己的货和行情走不出来。所以我昨天就说,应该就是这一两天会走出一波小行…

⑤【从0制作自己的ros导航小车:上、下位机通信篇】上、下位机串口DMA通信

从0制作自己的ros导航小车 前言一、准备工作二、下位机端(STM32)三、上位机端(旭日x3派)四、测试 系列文章: ①【从0制作自己的ros导航小车:介绍及准备】 ②【从0制作自己的ros导航小车:下位机篇…

一站式解决方案:打造无缝连接的跨渠道客户服务体验

在当今这个数字化时代,客户与企业之间的互动已不再局限于单一渠道。从社交媒体、在线聊天、电子邮件到电话热线,甚至是实体店面,客户期望能够随时随地、无缝切换地获得一致且高效的服务体验。因此,构建一站式解决方案,…

商城系统审计代码审计

1 开源组件通用性漏洞审计 1.1 fastjson漏洞审计与验证 1.1.1 相关知识 Fastjson是Alibaba开发的Java语言编写的高性能JSON库,用于将数据在JSON和Java对 象之间相互转换。 Fastjson反序列化漏洞简单来说是出现在将JSON数据反序列化过程中出现的漏洞。 攻击者可以传入一个恶…

算法小白的进阶之路(力扣6~8)

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 非常期待和您一起在这个小…

对象属性值对比(支持复杂对象)

文章目录 前言一、如何对比二、开始编码三、使用结果示例总结 前言 需求如下: 对比两个bean中的内容,返回其中属性的值不一致的完整信息,包括: 属性 新值 旧值 一、如何对比 例如我有一个这的类型: public class Tel {private String name;private String tel; …

学习笔记第十七天

1.链表 1.1链表尾插 void push_back(struct Node *pHead,int n)//尾插 {if(isEmpty(pHead)){push_front(pHead,n);}else{struct Node *p pHead->next; while(p->next !NULL){p p->next;}struct Node *pNew malloc(sizeof(struct Node));p->nextpNew;pNew->n…

C++ bind复杂回调逻辑分析

回调函数基本知识回顾 回调函数是什么 函数指针或者函数对象作为参数传递给另一个函数的机制,当某个事件发生的时候,系统会自动的调用这些函数进行处理事件驱动模型中作用,回调函数则被用于处理I/O事件,通常用来读写异常等事件 bi…

本科阶段最后一次竞赛Vlog——2024年智能车大赛智慧医疗组准备全过程——2Yolo使用之ONNX模型准备

本科阶段最后一次竞赛Vlog——2024年智能车大赛智慧医疗组准备全过程——2Yolo使用之ONNX模型准备 ​ 大家好,因为板端BPU环境,可以加速目标检测的速度,所以今天在此先给大家带来如何准备一个模型,下一期会给大家带来如何在板端部…

如何做一个惊艳领导和客户的原型?

在产品开发过程中,原型设计是验证设计想法、提升用户体验的重要环节。Axure作为一款业界领先的原型设计工具,凭借其强大的交互设计和丰富的功能,赢得了全球设计师和开发者的信赖。而Axure的高效交互元件库,则如同一本字典或说明书…

将YOLOv8模型从PyTorch的.pt格式转换为OpenVINO支持的IR格式

OpenVINO是Open Visual Inference & Neural Network Optimization工具包的缩写,是一个用于优化和部署AI推理模型的综合工具包。OpenVINO支持CPU、GPU和NPU设备。 OpenVINO的优势: (1).性能:OpenVINO利用英特尔CPU、集成和独立GPU以及FPGA的强大功能提…

原生IP节点是什么意思?和socks5节点有什么区别?

在了解这两种代理节点前,我们首先要了解:节点是什么? 首先,在电信网络当中,一个节点是一个连接点。表示一个再分发点又或者是一个通信端点。节点的定义依赖于所提及的网络和协议层。一个物理网络节点是一个连接到网络…

深度强化学习:穿越智能迷雾,探索AI新纪元

近年来,深度强化学习成为关注的热点。在自动驾驶、棋牌游戏、分子重排和机器人等领域,计算机程序能够通过强化学习,理解以前被视为超级困难的问题,取得了令人瞩目的成果。在围棋比赛中,AlphaGo接连战胜樊麾、李世石和柯…

使用 SpringBoot + 虚拟线程将服务性能提升几百倍

虚拟线程简介 虚拟线程是 Java 平台的一项创新特性。虚拟线程是一种轻量级的线程实现,它在操作系统层面并不对应真实的内核线程,而是由 JVM 进行管理和调度。这使得可以在不消耗大量系统资源的情况下创建大量的线程,从而能够更高效地处理并发任务。 虚拟线程与普通线程的区…