引入

并查集(Disjoint Set Union,DSU)是一种用于管理元素分组的数据结构。

合并(Union):将两个不相交的集合合并为一个集合。
查找(Find):确定某个元素属于哪个集合,通常通过返回集合的“代表元素”(groupID或父节点)实现。

quickFind 和 quickUnion 是并查集的两种实现方式。

每个元素初始时是一个独立的集合,其groupID是本身下标或父节点指向自己(分别表明各自属于哪个集合)。
如下:
在这里插入图片描述
主要就是对两个数组所存的内容进行操作,特别是代表元素部分。
对代表元素进行操作的方向(思考角度)不同,就会使用不同的解决方案(如选择quickFind还是quickUnion,)

quickFind

每个元素直接指向其所属集合的代表元(根节点),合并操作时需要遍历整个数组更新所有相关元素。

时间复杂度:
查找(Find):O(1),直接访问数组即可确定所属集合。
合并(Union):O(n),需要遍历数组更新所有属于同一集合的元素。

特点:查找速度快,但合并效率低(找快合慢)。

在这里插入图片描述

头文件

#pragma once
typedef int Element;typedef struct {Element* data;	 //存储具体数据组编号int* groupID;    //存储组编号int n;           //元素个数
}QuickFindSet;QuickFindSet* createQuickFindSet(int n);		//创建n个元素的quickFind树(快查树)
void releaseQuickFindSet(QuickFindSet* setQF);	//释放setQF这个树
void initQuickFindSet(const QuickFindSet* setQF, const Element* data, int n);//初始化setQF这个含n个结点的快查树,以及data数组的值(groupID数组初始时常以下标为值,依次赋值就行了)
//查,判断两个元素是不是同一个组
int isSameQF(QuickFindSet* setQF, Element a, Element b);
//并,合并两个元素
void unionQF(QuickFindSet* setQF, Element a, Element b);

功能实现

创建

QuickFindSet* createQuickFindSet(int n) {QuickFindSet* setQF = (QuickFindSet*)malloc(sizeof(QuickFindSet));if (setQF == NULL) {printf("setQF malloc failed!\n");return NULL;}//若申请成功,就传递n的值并继续申请两个数组的空间setQF->n = n;setQF->data =(Element*) malloc(sizeof(Element) * n);setQF->groupID = (int*)malloc(sizeof(int) * n);return setQF;
}

释放

void releaseQuickFindSet(QuickFindSet* setQF) {if (setQF) {if (setQF->data) {free(setQF->data);}if (setQF->groupID) {free(setQF->groupID);}free(setQF);}
}

初始化

void initQuickFindSet(const QuickFindSet* setQF, const Element* data, int n) {for (int i = 0; i < n; ++i) {setQF->data[i] = data[i];setQF->groupID[i] = i;}
}

查找目标值的索引

static int findIndex(const QuickFindSet* setQF, Element e) {for (int i = 0; i < setQF->n; ++i) {if (setQF->data[i] == e) {return i;}}return -1;
}

判断目标值索引对应的组ID是否相同(a、b在不在一个集合)

int isSameQF(QuickFindSet* setQF, Element a, Element b) {int aIndex = findIndex(setQF, a);int bIndex = findIndex(setQF, b);if (aIndex == -1 || bIndex == -1) {return 0;}return setQF->groupID[aIndex] == setQF->groupID[bIndex];
}

合并操作

void unionQF(QuickFindSet* setQF, Element a, Element b) {int aIndex = findIndex(setQF, a);int bIndex = findIndex(setQF, b);//假设把b集合中的元素合并到a集合上int gID = setQF->groupID[bIndex];		//先存下b的组号for (int i = 0; i < setQF->n; ++i) {if (setQF->groupID[i] == gID) {                 //是b组的元素setQF->groupID[i] = setQF->groupID[aIndex]; //全部组号改为a组的组号}}
}

功能调用

void test250808() {int n = 9;QuickFindSet* QFSet = createQuickFindSet(n);Element data[9];for (int i = 0; sizeof(data) / sizeof(data[0]); ++i) {data[i] = i;}initQuickFindSet(QFSet, data, n);unionQF(QFSet, 1, 3);unionQF(QFSet,3, 2);unionQF(QFSet, 2, 4);if (isSameQF(QFSet, 0, 2)) {printf("Yes\n");}else {printf("No\n");}unionQF(QFSet, 5, 1);if (isSameQF(QFSet, 5, 2)) {printf("Yes\n");}else {printf("No\n");}releaseQuickFindSet(QFSet);
}int main() {test250808();
}

quickUnion

使用树结构表示集合(看下图只能体现链式存储,后面的内容会讲到路径压缩:通过增大节点的度来提高效率会体现出树的特点),每个节点的parent指向其父节点,根节点
的parent指向自身,合并时将一个树的根指向另一个树的根就能连接两个树。

时间复杂度:
查找(Find):O(logn)(平均,取决于树高),需要递归或迭代找到根节点。
合并(Union):O(logn),仅需修改根节点的指向。

特点:合并效率高,但查找速度取决于树高。

在这里插入图片描述
这里是子节点指向父节点;父节点是自己时就指向自己,这种节点被称为根节点(如1和5)。
在这里插入图片描述
quickUnion查找速度取决于树高,可通过路径压缩等进一步提升性能:

路径压缩

路径压缩就是想办法把树变矮。像上面的例子:从1到4这串右子树,如果从根节点开始往下遍历,将每个节点的parent都直接指向根节点1,当根节点查找目标节点时仅需访问1层。

如何实现这一思想呢?遍历这些节点以及将它们每个节点的parent指向根节点这一环节时,可以想象成从叶子节点开始挨个摘取并存下来(直接指向根节点的叶子节点忽略),遍历到根节点时再挨个取出来并挨个将节点的parent指向根节点。

这种存储特点就不由的联想起了栈,相对提前申请空间的顺序栈,链式栈更合适,用多少就申请多少。且需采用头插法进行入栈操作(方便便利插入节点之后的节点元素)。
在这里插入图片描述

与quickFind的代码很相似,个别地方需调整。

头文件

#pragma once
typedef int Element;typedef struct {Element* data;int* parent;int* size;  //表示其中某个集合的元素个数int n;		//表示总元素个数(所有集合的元素个数之和)
}QuickUnionSet;//为压缩存储中链式栈的入栈出栈操作做准备:定义节点结构
typedef struct _set_node {int index;struct _set_node* next;
}SetNode;QuickUnionSet* createQuickUnionSet(int n);
void releaseQuickUnionSet(QuickUnionSet* setQU);
void initQuickUnionSet(QuickUnionSet* setQU, const Element* data, int n);int isSameQU(const QuickUnionSet* setQU, Element a, Element b);
void unionQU(QuickUnionSet* setQU, Element a, Element b);

功能实现

创建

QuickUnionSet* createQuickUnionSet(int n) {QuickUnionSet* setQU = (QuickUnionSet*)malloc(sizeof(Element) * n);if (setQU == NULL) {return NULL;}setQU->data = (Element*)malloc(sizeof(Element) * n);setQU->parent = (int*)malloc(sizeof(int) * n);setQU->size = (int*)malloc(sizeof(int) * n);setQU->n = n;return setQU;
}

释放

void releaseQuickUnionSet(QuickUnionSet* setQU) {if (setQU) {if (setQU->data) {free(setQU->data);}if (setQU->parent) {free(setQU->parent);}if (setQU->size) {free(setQU->size);}}
}

初始化

void initQuickUnionSet(QuickUnionSet* setQU, const Element* data, int n) {for (int i = 0; i < n; i++) {setQU->data[i] = data[i];setQU->parent[i] = i;setQU->size[i] = 1;}
}

查找目标值索引

static int findIndex(const QuickUnionSet* setQU, Element e) {for(int i = 0; i < setQU->n; ++i) {if (setQU->data[i] == e) {return i;}}return -1;
}

路径压缩

#define PATH_COMPRESS
#ifndef PATH_COMPRESS
static int findRootIndex(const QuickUnionSet* setQU, Element e) {int curIndex = findIndex(setQU, e);if (curIndex == -1) {return -1;}
//若找到则向上遍历,直到父节点和自身索引一致(找到根节点)while(setQU->parent[curIndex]!=curIndex) {curIndex = setQU->parent[curIndex];}return curIndex;
}
#else
static SetNode* push(SetNode* stack, int index) {
//传入的stack是一个空指针,相当于无头结点、头指针链式结构的头插操作SetNode* node = (SetNode*)malloc(sizeof(SetNode));node->index = index;node->next = stack;return node;
}static SetNode* pop(SetNode* stack, int* index) {SetNode* tmp = stack;
//主要任务是获得栈顶元素的索引*index = stack->index;//之后更新栈顶索引stack = stack->next;free(tmp);return stack;
}static int findRootIndex(const QuickUnionSet* setQU, Element e) {int curIndex = findIndex(setQU, e);if (curIndex == -1)return -1;
//找根的路径:从目标节点开始往上找,途经的节点都挨个入栈,到根节点为止SetNode* path = NULL;while (setQU->parent[curIndex] != curIndex) {path = push(path, curIndex);curIndex = setQU->parent[curIndex];}
//出栈,并将出栈的每个节点的parent都指向根节点while (path) {int pos;path = pop(path, &pos);setQU->parent[pos] = curIndex;}return curIndex;
}
#endif // !PATH_COMPRESS

判断两集合根的异同

int isSameQU(const QuickUnionSet* setQU, Element a, Element b) {int aRoot = findRootIndex(setQU, a);int bRoot = findRootIndex(setQU, b);if (aRoot == -1 || bRoot == -1) {return 0;}return aRoot == bRoot;
}

合并

/* 将元素a和元素b进行合并* 1. 找到a和b的根节点,原本是b的父节点指向a的父节点* 2. 引入根节点的size有效规则,谁的元素多,让另外一个接入元素多的组*/void unionQU(QuickUnionSet* setQU, Element a, Element b) {int aRoot = findRootIndex(setQU, a);int bRoot = findRootIndex(setQU, b);if (aRoot == -1 || bRoot == -1) {return;}if (aRoot != bRoot) {    // a和b不在一个集合里
// 根据根节点的索引找到对应size空间里保存的根所在树的总节点数//初始时size都为1,节点都是单独占一个集合,结合时再将两集合的size相加int aSize = setQU->size[aRoot];int bSize = setQU->size[bRoot];if (aSize >= bSize) {	// 将b元素的根节点指向a元素的根节点setQU->parent[bRoot] = aRoot;setQU->size[aRoot] += bSize;}else {setQU->parent[aRoot] = bRoot;setQU->size[bRoot] += aSize;}}
}

功能调用

void test250810() {int n = 9;QuickUnionSet* QUSet = createQuickUnionSet(n);Element data[9];for (int i = 0; i < sizeof(data) / sizeof(data[0]); ++i) {data[i] = i;}initQuickUnionSet(QUSet, data, n);unionQU(QUSet,1, 3);unionQU(QUSet, 3, 2);unionQU(QUSet, 2, 4);if (isSameQU(QUSet, 0, 2)) {printf("Yes\n");}else {printf("No\n");}unionQU(QUSet, 5, 1);if (isSameQU(QUSet, 5, 2)) {printf("Yes\n");}else {printf("No\n");}releaseQuickUnionSet(QUSet);
}
int main() {test250810();
}

拜拜~

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在 Vue 中使用 ReconnectingWebSocket实现即时通讯聊天客服功能

在 Vue 中使用 ReconnectingWebSocketReconnectingWebSocket 是一个自动重连的 WebSocket 实现&#xff0c;非常适合在 Vue 项目中使用。下面是如何在 Vue 中集成和使用它的方法&#xff1a;搜索 "程序员老狼"安装 ReconnectingWebSocket首先&#xff0c;你需要安装…

智能体革命:网络安全人的角色重塑与突围指南

AI赋能千行百业的趋势不可逆转&#xff0c;当AI学会渗透测试&#xff0c;安全工程师的出路在哪里&#xff1f; 2025年8月7日&#xff0c;OpenAI正式发布GPT-5的消息刷屏科技圈。这个达到博士生水平的“统一”人工智能模型&#xff0c;将AI幻觉率降低60%&#xff0c;成本下降45%…

用于水T1值和脂肪分数量化的上半身自由呼吸磁共振指纹成像|文献速递-医学影像算法文献分享

Title题目Upper-body free-breathing Magnetic Resonance Fingerprinting applied tothe quantification of water T1 and fat fraction用于水T1值和脂肪分数量化的上半身自由呼吸磁共振指纹成像 01文献速递介绍磁共振指纹成像&#xff08;MRF&#xff09;是十年前推出的一种高…

Apache RocketMQ:消息可靠性、顺序性与幂等处理的全面实践

Apache RocketMQ 是一个高性能、高可靠的分布式消息中间件&#xff0c;广泛应用于异步通信、事件驱动架构和分布式系统中。本文深入探讨 RocketMQ 的消息可靠性、顺序性和幂等处理机制&#xff0c;结合 Redisson 分布式锁实现幂等消费&#xff0c;提供详细的代码示例和实践建议…

无服务器日志分析由 Elasticsearch 提供支持,推出新的低价层

作者&#xff1a;来自 Elastic Log Analytics Elastic Observability Logs Essentials 在 Elastic Cloud Serverless 上提供成本效益高、无麻烦的日志分析。 SREs 可以摄取、搜索、丰富、分析、存储和处理日志&#xff0c;而无需管理部署的运营开销。[](https://www.elastic.co…

(Arxiv-2025)Phantom-Data:迈向通用的主体一致性视频生成数据集

Phantom-Data&#xff1a;迈向通用的主体一致性视频生成数据集 paper是字节发布在Arxiv2025的工作 paper title&#xff1a;Phantom-Data: Towards a General Subject-Consistent Video Generation Dataset Code&#xff1a;链接 Abstract 近年来&#xff0c;主体到视频&#…

如何解决pip安装报错ModuleNotFoundError: No module named ‘mlflow’问题

【Python系列Bug修复PyCharm控制台pip install报错】如何解决pip安装报错ModuleNotFoundError: No module named ‘mlflow’问题 摘要 在Python开发中&#xff0c;pip install 报错是一种常见问题&#xff0c;尤其是在使用集成开发环境&#xff08;IDE&#xff09;如PyCharm时…

2020/12 JLPT听力原文 问题一 3番

3番&#xff1a;会社で女の人と男の人が話しています。女の人は倉庫に入るとき、どの順番で入口のボタンを押さなければなりませんか。 女&#xff1a;すみません。地下の倉庫に行って、資料を取ってきたいんですが、入口の開け方がわからなくて… 男&#xff1a;ああ、最近、管…

C#/.NET/.NET Core技术前沿周刊 | 第 49 期(2025年8.1-8.10)

前言 C#/.NET/.NET Core技术前沿周刊&#xff0c;你的每周技术指南针&#xff01;记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿&#xff0c;助力技术成长与视野拓宽。 欢迎投稿、推荐…

基于强化学习的目标跟踪 研究初探

强化学习 目标跟踪Visual tracking by means of deep reinforcement learning and an expert demonstratorYOLO 检测下基于 ETC-DDPG 算法的无人机视觉跟踪基于特征与深度强化学习方法的机器人视觉伺服技术研究高性能可拓展视频目标跟踪算法研究基于目标运动与外观特征的多目标…

排序与查找,简略版

数组的排序 排序的基本介绍 排序是将一组数据&#xff0c;按照一定顺序进行排列的过程 排序的分类&#xff1a; 内部排序&#xff1a; 一次性适用数据量小的情况 将需要处理的数据都加载到内部存储器中进行排序。包括交换式排序&#xff0c;选择式排序&#xff0c;插入式排序 外…

打靶日常-XSS(反射型和存储型)

目录 小皮: 1. 2.这里需要登录,我们之前爆破出账号密码在这里就可以用​编辑 登录之后:​编辑 使用工具: 先输入正确字符进行测试:aaa 进行测试: 3.换种控制台显示 结果:(使用f12大法) DVWA: 反射型XSS: 低: ​编辑 中:大小写绕过: ​编辑 也可以双写绕过: ​编…

二叉搜索树深度解析:从原理实现到算法应用----《Hello C++ Wrold!》(18)--(C/C++)

文章目录前言二叉搜索树&#xff08;二叉排序树或二叉查找树&#xff09;二叉搜索树的模拟实现二叉搜索树和有序数组二分查找的比较两个搜索模型作业部分前言 二叉搜索树&#xff08;Binary Search Tree&#xff0c;简称 BST&#xff09;作为一种重要的树形数据结构&#xff0…

牛客.空调遥控二分查找牛客.kotori和气球(数学问题)力扣.二叉树的最大路径和牛客.主持人调度(二)

目录 牛客.空调遥控 二分查找 牛客.kotori和气球&#xff08;数学问题) 力扣.二叉树的最大路径和 牛客.主持人调度(二) 牛客.空调遥控 枚举n个空调之后&#xff0c;使数组有序&#xff0c;左右下标&#xff0c;用二分查找&#xff0c;然后一个求 长度就好 二分查找 /二分理…

《嵌入式Linux应用编程(二):标准IO高级操作与文件流定位实战》

今日学习内容1. 行输入函数安全实践(1) fgets vs gets函数安全特性换行符处理缓冲区保护fgets指定读取长度&#xff08;size-1&#xff09;保留\n并添加\0安全&#xff08;防溢出&#xff09;gets无长度限制将\n替换为\0危险2. Linux标准文件流文件流符号设备 标准输入stdin键盘…

Springboot2+vue2+uniapp 小程序端实现搜索联想自动补全功能

目录 一、实现目标 1.1 需求 1.2 实现示例图: 二、实现步骤 2.1 实现方法简述 2.2 简单科普 2.3 实现步骤及代码 一、实现目标 1.1 需求 搜索联想——自动补全 &#xff08;1&#xff09;实现搜索输入框&#xff0c;用户输入时能显示模糊匹配结果 &am…

极简 5 步:Ubuntu+RTX4090 源码编译 vLLM

极简 5 步&#xff1a;UbuntuRTX4090 源码编译 vLLM1. 系统依赖&#xff08;一次性&#xff09;2. 进入源码目录 & 激活环境3. 启用 ccache 自动并行度4. 拉代码 编译&#xff08;2 行搞定&#xff09;5. 更新 flash-attn&#xff08;与 vLLM 配套&#xff09;6. 启动 4 …

生产工具革命:定制开发开源AI智能名片S2B2C商城小程序重构商业生态的范式研究

摘要互联网作为信息工具已深刻改变商业生态&#xff0c;但其本质仍停留在效率优化层面。本文提出&#xff0c;基于定制开发开源AI智能名片与S2B2C商城小程序的深度融合&#xff0c;正在引发生产工具层面的革命性变革。该技术架构通过重构"人-货-场"关系&#xff0c;实…

Transformer前传:Seq2Seq与注意力机制Attention

前言 参考了以下大佬的博客 https://blog.csdn.net/v_july_v/article/details/127411638 https://blog.csdn.net/andy_shenzl/article/details/140146699 https://blog.csdn.net/weixin_42475060/article/details/121101749 https://blog.csdn.net/weixin_43334693/article/det…

企业架构工具篇之ArchiMate的HelloWorld(2)

本文通过ArchiMate做一个员工报销流程设计的小demo,按照步骤都可以做出来,在做这个demo之前先简单认识下Archimate的开发界面: 模型树(Models)窗口:通常位于左上方,以树形结构展示一个或多个 ArchiMate 模型。用户可在此浏览模型的整体结构,快速定位到特定的模型元素,…