第四章不相交集数据结构Union-Find分析

合集下载

并查集解决元素分组及连通性问题

并查集解决元素分组及连通性问题

并查集解决元素分组及连通性问题并查集(Disjoint-set Union)是一种常用的数据结构,主要用于解决元素分组及连通性问题。

它将n个元素划分为若干个不相交的集合,每个集合通过一个代表来标识。

在该数据结构下,我们可以高效地进行合并集合和查询某个元素所属的集合操作。

一、并查集的基本原理并查集主要由两个操作组成:合并(Union)和查找(Find)。

其中,合并操作将两个集合合并为一个集合,查找操作则用于确定某个元素所属的集合。

在并查集中,每个元素都有一个指向父节点的指针,初始时每个元素自成一个集合,其父节点指向自己。

合并操作就是将两个元素所在集合的根节点合并为一个根节点,即将其中一个集合的根节点指向另一个集合的根节点。

查找操作通过递归地向上查找父节点,最终找到根节点,从而确定该元素所属的集合。

二、并查集的具体实现并查集的具体实现可使用数组或树结构。

数组实现简单直观,树结构实现更加高效。

以下是树结构实现的代码示例:```pythonclass UnionFind:def __init__(self, n):self.parent = list(range(n))def union(self, a, b):self.parent[self.find(a)] = self.find(b)def find(self, x):if self.parent[x] != x:self.parent[x] = self.find(self.parent[x])return self.parent[x]```在这个实现中,parent数组记录了每个元素的父节点,初始时每个元素的父节点为自己。

合并操作通过将某个元素的根节点指向另一个元素的根节点来实现。

查找操作递归地向上查找父节点,直到找到根节点。

三、应用场景举例1. 图的连通性判断:利用并查集可以判断图中的两个节点是否连通。

假设图中有n个节点,我们可以用并查集来维护这些节点的连通情况。

第四章不相交集数据结构Union-Find分析

第四章不相交集数据结构Union-Find分析
17
删除复杂度: 这些在算法DELETE中描述。有观察结论 3.4可知,堆树的高度是 log n ,所以从 一个大小为n的堆中删除一个元素所需要的 时间是O(log n)。
18
算法4.3 DELETEMAX//删除根节点 输入:堆H[1…n] 输出:返回最大值元素 x并将其从堆中删 除 1. x H[1] 2. DELETE(H,1) 3. Return x

ri r2i (大顶堆) ri r2i 1
例如:
{12, 36, 27, 65, 40, 34, 98, 81, 73, 55, 49} 是小顶堆 {12, 36, 27, 65, 40, 14, 98, 81, 73, 55, 49} 不是堆
5
若将该数列视作完全二叉树,则 r2i 是 ri 的左孩子; r2i+1 是 ri 的右孩子。
,根据递推式求解:O(logn)
14
算法4.1 INSERT 输入:堆H[1…n]和元素x 输出:新的堆H[1…n+1], x为其元素之 一 1. n= n+1{增加H的大小} 2. H[n] x 3. SIFT-UP(H, n)

15
插入 (1)先将堆大小加1,然后将x添加到H 的末尾,再根据需要,把x上移,直到满足 堆特性, (2)观察结论3.4可知,如果n是新堆的 大小,那么堆树的高度是 log n ,所以将 一个元素插入大小为n的堆中所需要的时间 是O(log n)。
31
给出数组A[1…n],将其中的元素用如 下方法以非降序有效排序。 step 1.首先将A变换成堆,并使其具有 这样的性质,每个元素的键值是该元素本 身,即key(A[i])= A[i],1 ≤i ≤n。

数据结构union函数 -回复

数据结构union函数 -回复

数据结构union函数-回复数据结构中的union函数是一种常见的操作,它用于合并两个集合或者查找两个元素所属的集合。

本文将详细介绍union函数的实现原理以及其在数据结构中的应用。

一、union函数概述在数据结构中,union函数的主要作用是将两个不相交的集合合并为一个集合,从而构建一个更大的集合。

具体来说,union函数的输入是两个集合以及它们的代表元素,输出是合并后的集合。

二、union函数的实现原理对于union函数的实现,常见的方法是使用并查集(disjoint set)数据结构。

并查集是一种用于处理不相交集合的数据结构,它支持合并集合和查询元素所属集合的操作。

在并查集中,每个集合用一棵树来表示,树的根节点指向自身,其他节点指向它的父节点。

每个集合由一个代表元素来表示,代表元素是根节点。

假设有两个集合A和B,分别由代表元素a和b表示,union函数的目标就是将集合A和B合并为一个集合。

具体而言,union函数的实现可以分为以下步骤:1. 首先,找到集合A和B的代表元素a和b。

2. 将代表元素b的父节点设置为a,即将集合B合并到集合A中。

3. 如果集合B的规模比集合A大,则更新集合A的代表元素为b。

可以通过路径压缩来优化union函数的性能。

路径压缩是一种在查找代表元素的过程中优化树形结构的方法,它通过将路径上的每个节点直接连接到根节点,减少树的高度,提高查找效率。

三、union函数的应用union函数在数据结构中有广泛的应用,下面分别介绍两个典型的应用场景。

1. 连通性问题在图论中,连通性问题是指判断两个节点是否存在路径连接的问题。

利用union函数可以高效地解决这个问题。

具体而言,可以使用一个并查集来表示图中的节点集合,每个节点用一个元素来表示。

当两个节点之间存在边时,可以使用union函数将它们所属的集合合并。

最后,利用find函数来判断两个节点是否属于同一个连通分量。

2. 集合合并在某些场合,需要将多个集合合并为一个更大的集合。

并查集__Union-Find_Sets_

并查集__Union-Find_Sets_

对于并查集来说,每个集合用一棵树表示。 对于并查集来说,每个集合用一棵树表示。 集合中每个元素的元素名分别存放在树的结 点中,此外, 点中,此外,树的每一个结点还有一个指向其 双亲结点的指针。 双亲结点的指针。 为此,需要有两个映射: 为此,需要有两个映射: 集合元素到存放该元素名的树结点间的对 应; 集合名到表示该集合的树的根结点间的对 应。 设 S1= {0, 6, 7, 8 },S2= { 1, 4, 9 },S3= { 2, 3, , , 5}
O(∑ i ) = O( n )
2 i =1
n
Union操作的加权规则 Union操作的加权规则
为避免产生退化的树, 为避免产生退化的树,改进方法是先判断两集合中元素 的个数, 的个数,如果以 i 为根的树中的结点个数少于以 j 为根 的树中的结点个数, 的树中的结点个数,即parent[i] > parent[j],则让 j 成为 , i 的双亲,否则,让i成为 的双亲。 的双亲,否则, 成为 的双亲。 成为j的双亲 此即Union的加权规则。 此即 的加权规则
下标
0
123ຫໍສະໝຸດ 4567
8
9
parent - 1
4
-1 2
-1 2
0
0 0
4
集合S1, S2和 S3的双亲表示
S1 U S2的可能的表示方法
®
const int DefaultSize = 10; class UFSets { //并查集的类定义 并查集的类定义 public: UFSets ( int s = DefaultSize ); ~UFSets ( ) { delete [ ] parent; } const UFSets & operator = ( UFSets const & Value ); void Union ( int Root1, int Root2 ); int Find ( int x ); void UnionByHeight ( int Root1, int Root2 ); private: int *parent; int size; };

算法与数据结构基础-合并查找(UnionFind)

算法与数据结构基础-合并查找(UnionFind)

算法与数据结构基础-合并查找(UnionFind)Union Find算法基础Union Find算法⽤于处理集合的合并和查询问题,其定义了两个⽤于并查集的操作:Find: 确定元素属于哪⼀个⼦集,或判断两个元素是否属于同⼀⼦集Union: 将两个⼦集合并为⼀个⼦集并查集是⼀种树形的数据结构,其可⽤数组或unordered_map表⽰:Find操作即查找元素的root,当两元素root相同时判定他们属于同⼀个⼦集;Union操作即通过修改元素的root(或修改parent)合并⼦集,下⾯两个图展⽰了id[6]由6修改为9的变化:图⽚来源Union Find算法应⽤Union Find可⽤于解决集合相关问题,如判断某元素是否属于集合、两个元素是否属同⼀集合、求解集合个数等,算法框架如下://261. Graph Valid Treebool validTree(int n, vector<pair<int, int>>& edges) {vector<int> num(n,-1);for(auto edge:edges){//find查看两点是否已在同⼀集合int x=find(num,edge.first);int y=find(num,edge.second);if(x==y) return false; //两点已在同⼀集合情况下则出现环//union让两点加⼊同⼀集合num[x]=y;}return n-1==edges.size();}int find(vector<int>&num,int i){if(num[i]==-1) return i;return find(num,num[i]); //id[id[...id[i]...]]}⼀些情况下为清晰和解偶会将Uinon Find实现为⼀个类,独⽴出明显的Union和Find两个操作。

18.用于不相交集合的数据结构

18.用于不相交集合的数据结构

(1)按秩合并
• 其思想是:合并时,使包含较少结点的树 的根指向包含较多结点的树的根。 • 秩:表示结点高度的一个上界。 • 因此,在按秩合并中,具有较小秩的根在 UNION操作中要指向具有较大秩的根。 • 即合并的时候将元素少的集合合并到元素 多的集合中,这样合并之后树的高度会相 对较小。
• 每个结点的秩是结点高度的一个上界,只 有在进行UNION操作时,而且进行合并操 作的两个集合的秩相同时,才会给最后的 根结点的秩+1。
算法导论
ACM/ICPC,编写我们的未来
18 用于不相交集合的数据结构
• 用于不相交集合的数据结构又称为并查集。 • 在很多的应用中(比如图的算法中经常使用 ,还有哈夫曼编码等),要将n个不同的元 素分成一组不相交的集合。 • 不相交集合上有两个重要的操作:
– 找出给定元素所属的集合 – 合并两个集合
3 不相交集合森林表示法
• 用有根树来表示集合,树中的每个结点都包 含集合的一个成员,每棵树表示一个集合。 • 每个成员仅指向其父结点,每棵树的根包含 了代表,并且是它自己的父结点。 • 例如:
• make_set(x) //把每一个元素初始化为一个 集合
– 创建一棵仅包含一个结点x的树 。
• find_set(x) //查找一个元素所在的集合
void make_Set(int x) { s[x]=new node; s[x]->rank=0; s[x]->p=s[x]; }
பைடு நூலகம்
node* find_Set(node* s) { if(s!=s->p) { s->p=find_Set(s->p); } return s->p; }

程序设计中常用的数据结构

程序设计中常用的数据结构

程序设计中常用的数据结构在程序设计中,数据结构是许多算法和程序的基础。

以下是程序设计中常用的几种数据结构:1. 数组(Array):数组是一组有序的数据元素,可以通过索引值来访问和修改数组中的元素。

数组的主要优点是支持随机访问,但插入和删除操作的效率相对较低。

2. 栈(Stack):栈是一种后进先出(LIFO)的数据结构,只能从栈顶插入和删除数据。

栈的主要应用场景包括函数调用、表达式求值和内存分配等。

3. 队列(Queue):队列是一种先进先出(FIFO)的数据结构,只能从队列尾插入数据并从队列头删除数据。

队列的主要应用场景包括广度优先搜索和任务调度等。

4. 链表(Linked List):链表是一种递归的数据结构,由若干个节点组成,每个节点包含当前元素的值和指向下一个节点的指针。

链表支持快速插入和删除操作,但访问特定位置的元素需要顺序查找,效率相对较低。

5. 哈希表(Hash Table):哈希表是一种基于哈希函数实现的数据结构,可以快速地插入、删除和查找元素。

在哈希表中,元素的存储位置是通过哈希函数计算得到的,因此访问特定位置的元素效率很高。

6. 树(Tree):树是一种层次结构,由若干个节点和连接这些节点的边组成。

常见的树形数据结构包括二叉搜索树、红黑树、AVL 树和 B 树等。

树的主要应用场景包括搜索、排序和存储等。

7. 图(Graph):图是由一组节点和连接这些节点的边组成的数据结构。

图常用来表示实体之间的关系,如社交网络、路线图等。

在计算机科学中,图的主要应用包括最短路径算法、网络流等。

8. 堆(Heap):堆是一种特殊的树形数据结构,它满足某些特定的性质。

被称为“最小堆”的堆中,每个父节点的值都小于或等于其子节点的值,而被称为“最大堆”的堆中,每个父节点的值都大于或等于其子节点的值。

堆可以用来实现优先队列和堆排序等算法。

9. 字典树(Trie):字典树是一种用于字符串处理的树形数据结构。

数据结构 第4章

数据结构 第4章



例子

假设要建立一个地址区间长度为13的哈希表,哈希函数为 H(key) = Ord(关键字第一个字母)-1)/2 其中函数Ord求字母在字母表中的序号。例如,字母A在 字母表中的序号为1, Ord(‘A’)=1。 现将关键字依次为Zhao, Qian, Sun, Li, Wu, Chen, Han的 7 个记录插入该哈希表。


例子

例如构造一个数据元素个数n = 60,哈希地址空间长度m = 100 的哈希表。 对关键字分析发现,关键字的第 1、2、3、6位取值比较集中,不 宜作为哈希地址,
…… 8 8 8 8 8 8 8 8 1 1 2 1 2 2 1 1 3 3 7 3 7 7 3 3 1 2 3 4 0 7 8 7 6 9 3 6 4 1 6 8 6 6 8 6 2 0 7 2 3 1 3 6 2 5 8 4 5 8 2
数据结构
广东工业大学 计算机学院
第4章 哈希表
第4章 哈希表


4.1 哈希表的概念 4.2 哈希函数的构造方法

4.2.1 直接定址法 4.2.2 除留余数法 4.2.3 数字分析法 4.2.4 折叠法 4.2.5 平方取中法 4.3.1 开放定址法 4.3.2 链地址法 链地址哈希表的实现 开放定址哈希表的实现
移位叠加 0040 1108 1053 0216 + 9891 (1)2308

Z形叠加 0040 8011 1053 6120 + 9891 (2)5115
4.2.5 平方取中法


平方取中法先取关键字的平方,然后根据哈希表地址区 间长度m的大小,选取平方数的中间若干位作为哈希地 址。通过取平方扩大关键字之间的差别,而平方值的中 间若干位和这个数的每一位都相关,使得不同关键字的 哈希函数值分布较为均匀,不易产生冲突。 设哈希表地址区间长度为1000,可取关键字平方值的中 间三位。

数据库课程设计树形等价类的Union,Find文档

数据库课程设计树形等价类的Union,Find文档

山东大学软件学院数据结构课程设计报告设计题目: 树形等价类的Union,Find学号姓名年级专业软件工程班级学期12-13学年第二学期日期: 2013年3 月5日1.需求描述1)准确演示算法的实现思想,把算法的过程思想一步步展示出来。

让一个不懂数据结构的人,看了演示程序,就能对算法的思路大体了解。

2)能单步演示。

3)软件要能用、易用。

界面友好。

4)软件可以对输入用例案例重复演示。

不能演示完成就自动退出。

5)输入的用例数据可编辑。

6)软件要健壮,对非法的输入能给出合适的提示,不能直接导致软件崩溃。

2.实现思想1)有一个Leaf类,每个Leaf对象有一个父亲节点(初始化时为null),有一个链表里面有这个节点的所有孩子节点。

当添加等价关系时,其实添加的是节点间的父子关系,代码举例:l1.getChild().add(l2);l2.setParent(l1);2)其实程序开始时,初始化了72个Leaf元素,当用户输入元素个数n,则把num变量设为n,把num个元素设为可见。

当然用户输入的数据大于72时异常处理会提醒用户输入0到72之间的数。

3)每个Leaf对象都有一个height属性,当需要显示树的时候,统计每个height上有多少个Leaf对象,根据Leaf的个数来确定间隔,则画出的节点不易重叠。

用链表来盛放所有从当前元素到根节点经过的所有节点。

4)当用户点击元素个数后面的提交按钮时,将所有的等价关系清空,来实现可重复使用.5)程序里已经设计好一组等价关系,如果点自动按钮,屏幕上会出现两个等价类,用户可以据此做出查找,合并等操作.6)利用链表来盛放从文本框中元素到根节点的所有元素,利用线程来控制每个节点变化的时间间隔,实现动画效果.另外写了一个sign类,声明“有父亲”和“根”两个对象,通过设置这两个对象的位置来对动画进行说明。

7)每次获取文本框中的元素后,会用一个链表来放从该元素到根节点的所有元素,然后每次点击单步时,判断链表是否为空,如果为空则下一步按钮失效.以此实现单步查找.3.数据结构设计1)每个节点的孩子用链表来放.2)用一个集合来盛放屏幕上所有的节点for (int i = 0; i < 72; i++) {int x = i % 24 * 35;int y = i / 24 * 35;Leaf lf = new Leaf(i + 1, newArrayList<Leaf>(), false);al.add(lf);}3)利用链表来放每层的元素,默认设为9层,如果树太高,超过9层,则提示用户.4)用already集合来盛放所有已经添加过等价关系的节点5)而画树的时候则仿照二叉树的层次遍历,利用队列LinkedList<Leaf> q = new<Leaf> LinkedList();q.add(l);LinkedList<Leaf> list = new<Leaf> LinkedList();while (q.peek() != null) {Leaf leaf1 = q.poll();leaf1.setVisible(true);4.功能设计1)基本功能:a)元素个数后面的文本框用来给用户输入数字,即元素的个数.如果输入的数字不在0到72的范围内,那么会提示用户,请用户输入0到72之间的正整数.b)等价关系后面的两个文本框用来让用户输入有等价关系的两个数,如果用户输入的数字比前面元素个数大,那么会提示用户说输入的数字太大. 当用户点击等价关系后面的提交时,右面的面板上就会有对应的演示,先演示如何寻找两个元素对应的根,再演示添加这组等价关系.同时右面屏幕的上方会显示对应的说明.c)查找后面的数字可以更改,也可以用自带的,来查找某个元素所在的等价类.同时右边会有对应的动画演示,右面上方也会有对应的解释.d)合并用来合并两个等价类.分别查找两个文本框中元素所在等价类,如果已经在同一个等价类,则提醒用户已经是等价的,如果不在同一个等价类,则默认把右边文本框中元素的根,添加为左边文本框中元素根的孩子.2)扩展功能:a)实现自动,即程序本身自带一组数据,如果用户不想输入,可以用程序自带的数据来演示.b)实现可重复使用,用户可以重复输入数据来演示c)实现单步查找.d)实现动画效果,5.运行环境说明6.操作说明如果用户不想手动输入数据,那么可以点击左下方最后一行的自动按钮,这时候屏幕上会出现两棵树,用户可以进行等价关系的添加,查找,合并,单步查找等操作.如果用户想自己操作,那么先输入元素个数,并提交,再进行等价关系的添加,查找,合并,单步查找等操作,可以重复进行.7.系统测试用例元素个数16等价关系:1和3,5和7,3和5,2和8(每输入一组等价关系要点击提交按钮) 查找:7合并:7和8,2和88.收获及体会1)先做好规划,做好总体的设计,再开始写代码,这次写程序急着写,结果后来需要不断修改,延误了时间,代码也比较乱2)写代码一定要规范,变量声明,取名要统一,多添加注释3)遇到bug不要着急,更不能放弃,有些时候往往最简单的错误导致很难找的bug4)尽量地节省内存9.改进说明1)在演示区域的上方添加了说明来解释程序的动画2)当用户添加等价关系时会有对应的动画来显示添加的过程3)查找的文本框由开始的两个变为一个。

unionfind算法

unionfind算法

unionfind算法unionfind算法是一种用于解决动态连通性问题的算法,它的数据结构是一组以节点表示的数据集合,用于保存每个节点的父节点索引(有时也称为代表)。

unionfind算法使用一组连接来表示节点之间的关系,可以将不同节点连接在一起,也可以从两个节点之间断开连接。

unionfind算法最主要的操作是 Union(连接)和 Find(查找),它们可以用来检查两个节点是否连接,以及将不同的节点连接在一起。

unionfind算法是一种支持快速查找和连接的数据结构,用于动态解决连接问题,如寻找网络中所有连通分量、基于关键路径分析等。

unionfind算法可以帮助我们在不知道节点之间的具体联系下,快速地进行查找和连接。

unionfind算法的实现具有两个关键步骤:Find和Union,它们的功能分别是查找和连接。

具体而言,Find操作可以用来查找节点的父节点,如果两个节点的父节点是相同的,则说明两个节点是连通的;而Union操作则可以用来将两个节点连接起来。

unionfind算法也有自己的优点和不足之处。

它的优点在于,它可以有效地检查两个节点之间是否连通,以及将不同节点连接起来,可以显著缩短查找和连接的时间;而它的不足之处则是查询父节点的Find作可能会随着树的层数而变慢,且由于每次连接操作都要改变所有节点的父节点,因此union操作的时间复杂度也会随着树的层数而变高。

因此,要想有效利用 unionfind法,就必须要有良好的实现,并且需要考虑如何改进算法的时间复杂度,以提高效率。

例如,使用并查集结构时可以采用加权和路径压缩等优化算法,以提高unionfind运行效率。

总之,unionfind算法是一种非常有用的算法,它可以帮助我们在不知道节点之间具体的联系的情况下快速查找和连接节点,提高查找和连接的效率。

但unionfind算法也有它的不足之处,因此,如果要有效地利用它,就需要对算法进行相应的优化,以提高unionfind 算法的效率。

第四章堆和不相交集数据结构

第四章堆和不相交集数据结构

第四章堆和不相交集数据结构一、堆的定义n 个元素称为堆,当且仅当他的关键字序列K 1 , K 2....Kn 满足K i K2i。

&& K i K2i+1 1 i n / 2或者满足 K i K2i。

&&K i K2i+11 in / 2 分别称为最小堆和最大堆。

性质:堆可以看成是一颗完全二叉树。

如果将一棵有n 个结点的完全二叉树的结点按层序(自顶向下,同一层自左向右)连续编号 1, 2, , n,然后按此结点编号将树中各结点顺序地存放于一个一维数组中, 并简称编号为i 的结点为结点i (1i n)。

则有以下关系:若 i == 1, 则 i 是二叉树的根,无双亲若 i > 1, 则 i 的双亲为i /2若 2* i ≤ n, 则 i 的左孩子为 2*i,否则无左孩子若 2* i+1 ≤ n, 则 i 的右孩子为 2* i+1,否则无右孩子i 所在层次为log i +1若设二叉树的深度为h,则共有 h 层。

除第 h 层外,其它各层 (0 h-1)的结点数都达到最大个数,第h层从右向左连续缺若干结点具有n个结点的完全二叉树的深度为logn +1二、堆上运算在此考虑最大堆。

1.Sift-up: 把堆上的第i 个元素上移;2.Sift-down: 把堆上的第i 个元素下移;3.Insert: 把元素 x 插入堆中;4.Delete:删除堆中第i 个元素;5.删除最大元素6.Makeheap: 创建堆;7.Heapsort:对排序;1.过程Sift-up输入:数组H[1..n] 和位于 1 和 n 之间的索引i输出:上移 H[i](如果需要),以便使他不大于父结点(大堆)Done ← falseIf i=1 then exitRepeatIf key(H[i])>key(H i / 2 ) then 互换 H[i] 和 (H i / 2 elsedone ←truei←i / 2until i=1 or done例 :分析:执行时间O( logn) ,空间(1)2.过程Sift-down输入:数组H[1..n] 和位于 1 和 n 之间的索引i输出:下移H[i] (如果需要),以便使他不小于子结点Done ← falseIf 2i 〉 n then exit{节点是叶节点 }Repeati←2iIf If i+1 n andkey (H i / 2key(H[i+1])>key(H[i]))<key(H[i]) then互换(Hthen i←i+1i / 2 )和H[i]else done ←trueendifuntil 2i>n or done例 :分析:执行时间O( logn) ,空间(1)算法 4.1 insert思想:先插到最后,然后上移输入:堆 H[1..n] 和元素 x输出:新的堆H[1 n+1],x 为其元素之一1.n←n+1 {增加H的大小}2.H[n] ←x3.Sift-up(H,n)例 :分析:执行时间O( logn) ,空间(1)算法 4.2 delete思想:先将最后元素替代H[i] ,然后调整输入:非空堆 H[1..n] 和位于 1 和 n 之间的索引 i 输出:删除 H[i] 之后的新堆 H[1..n-1]1.x←H[i] ;y← H[n] ;2.n←n-1 {减小H的大小}3. if i=n+1 then exit {完成 }4.H[i] ←y5. if key(y)key(x) then sift-up(H,i)6.else Sift-down(H,i)7.end if例 :分析:执行时间O( logn) ,空间(1)deleteMAX输入:非空堆H[1..n]输出:返回最大键值元素x 并从堆中删除1.x←H[1]2.Delete(H,1)3.Return x创建堆n 个元素,连续方法一:假设从一个空堆开始,对数组中的地使用 insert 操作插入堆中。

并查集

并查集

并查集--学习详解文章作者:yx_th000文章来源:Cherish_yimi (/cherish_yimi/) 转载请注明,谢谢合作。

昨天和今天学习了并查集和trie树,并练习了三道入门题目,理解更为深刻,觉得有必要总结一下,这其中的内容定义之类的是取自网络,操作的说明解释及程序的注释部分为个人理解。

并查集学习:●并查集:(union-find sets)一种简单的用途广泛的集合. 并查集是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作,应用很多,如其求无向图的连通分量个数等。

最完美的应用当属:实现Kruskar算法求最小生成树。

●并查集的精髓(即它的三种操作,结合实现代码模板进行理解):1、Make_Set(x) 把每一个元素初始化为一个集合初始化后每一个元素的父亲节点是它本身,每一个元素的祖先节点也是它本身(也可以根据情况而变)。

2、Find_Set(x) 查找一个元素所在的集合查找一个元素所在的集合,其精髓是找到这个元素所在集合的祖先!这个才是并查集判断和合并的最终依据。

判断两个元素是否属于同一集合,只要看他们所在集合的祖先是否相同即可。

合并两个集合,也是使一个集合的祖先成为另一个集合的祖先,具体见示意图3、Union(x,y) 合并x,y所在的两个集合合并两个不相交集合操作很简单:利用Find_Set找到其中两个集合的祖先,将一个集合的祖先指向另一个集合的祖先。

如图●并查集的优化1、Find_Set(x)时路径压缩寻找祖先时我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次Find_Set(x)都是O(n)的复杂度,有没有办法减小这个复杂度呢?答案是肯定的,这就是路径压缩,即当我们经过"递推"找到祖先节点后,"回溯"的时候顺便将它的子孙节点都直接指向祖先,这样以后再次Find_Set(x)时复杂度就变成O(1)了,如下图所示;可见,路径压缩方便了以后的查找。

1简单不相交集的合并算法

1简单不相交集的合并算法

简单不相交集的合并算法本节的假定前提:1、为算法书写方便起见,设任一集合都是{1,2,…n}的子集;2、任意两个被合并的集合都是不相交的;3、集合上的运算只有Union和Find。

Union(I,J,K):把名为I与J的集合进行合并,合并后的集合名为K。

∵初始总共有n个单元素集,故Union最多可执行n-1次。

Find(a):给出a所在的集合名(算法中大多用数字表示集合名)。

通常Find指令的执行也有O(n)次,故此类问题通常都是讨论执行O(n)条Union和Find指令所需要的时间。

可以用来表示集合的数据结构很多,用什么样的算法和结构才能使得完成上述任务的时间最少? Union(I,J,K) 算法中的数组说明为加快处理速度,每个集合给予一个内部名和一个外部名。

内部名与外部名1-1对应。

例如:External-Name[S]:内部名为S(数字)的集合所对应的外部名。

Internal-Name[L]:外部名为L(数字)的集合所对应的内部名。

R[i]:给出元素i所属集合的内部名。

(Find指令O(1)时间完成)Next[i]:给出与元素i同在一个集合中的下一个元素,内容为0时,表示无下一元素(即元素i是该集合的最后一个元素)。

List[S]:给出内部名为S的集合中的第一个元素。

Size[S]:给出内部名为S的集合中的元素个数。

1.A←Internal-Name[I]; /*将集合外部名I,J转为内部名A和B*/2.B←Internal-Name[J];3.wlg assume Size[A] ≤ Size[B] /* A为小集合,B为大集合*/4.otherwise interchange roles of A and B in5.{ELEMENT←List[A]; /*找出集合A的第一个元素*/6.while ELEMENT ≠ 0 do/*不断把A中元素的所在集合名改为B,直到全部改完为止*/7.{R[ELEMENT]←B; /*改名*/ST←ELEMENT; /*记下当前元素*/9.ELEMENT←Next[ELEMENT]; /*当前元素更新*/}/*循环结束时,LAST中记录了原集合A中的最后一个元素*/ 10.Next[LAST]←List[B];/*置该元素的下一个元素为原B中的第一个元素,*//*从而实现A和B的合并*/11.List[B]←List[A]; /*置合并后的首元素为原A中首元素*/12.Size[B]←Size[A] + Size[B];/*置集合大小为2集合的规模之和*/ 13.Internal-Name[K]←B;/*建立新集合的内部名与外部名的对应关系*/ 14.External-Name[B]←K} Union算法除6-9行以外,其余均为常数时间。

UnionFind(并查集)-Disjoint-SetDataStructure

UnionFind(并查集)-Disjoint-SetDataStructure

最后得到的树的高度 大幅度减小了
find方法的效率增加!
Union-Find算法3-Weighted Union with Path Compression!
• What can be further improved? All tree are with height == 1!
– 所有的孩子节点应该都在height为1的地方== – 所有的孩子都直接连接到根节点== – 保证find操作的最高效率的组织结构
• For Disjoint-set data structure, the best solution is Union-Find with path compression
• 2 Tricks improve O(n) O(lgn) O(lglglg..lgn)
– Smaller tree merged into larger tree – Path compression
• Path Compression_2(Textbook Page59):
Cost of find is still
Complexity
相比课本P59最后一段的path compression,节约了保存中 间路径的开销。均摊效率几 乎相等,no visible difference.
Conc过若干次查找,一个节 点总能找到它的根节点, 即满足id[root] = root的节 点,i.e.组的根节点
Union-Find算法1-QuickUnion
• 采用parent-link的方式将 节点组织起来.
• id[p]的值就是p节点的父 节点的序号
--对每个联通组,要能快速知道一个独一 无二的代表性元素. --对每个元素,要能快速查询所在连通 分支组号.

union-find抽象数据结构

union-find抽象数据结构

union-find抽象数据结构UNION - FIND算法是简单不相交集的合并算法。

在一些应用问题中,需要将n个不同的元素划分成若干组,每一组元素构成一个集合。

这种问题的---个解决办法是:开始时,让每个元素组成一个单元素集合,然后按一定顺序将属于同一组的元素所在集合合并,其间要反复用到查找--个元素在哪个集合的运算。

它涉及到的操作主要有UNION和FIND两种:UNION(S1,S2,S3)——-并操作,将集合S1与集合S2合并,形成新的集合S3;FIND(a)——搜索操作,搜索元素a所在的集合,并返回该集合的名字。

算法的复杂度是算法效率的度量,是评价算法优劣的重要依据。

一个算法的复杂度的高低体现在运行该算法所需要的计算机资源的多少上面,所需的资源越多,我们就说该算法的复杂度越高;反之,所需的资源越低,则该算法的复杂度越低。

计算机的资源,最重要的是时间和空间(即存储器)资源。

因而,算法的复杂度有时间复杂度和空间复杂度之分。

在这里,我们主要研究算法的时间复杂度,它是算法所求解问题规模n的函数。

UNION - FIND问题主要涉及到UNION和FIND 两种操作,解决该问题需要考虑的重要因素就是如何同时保证在最短时间内完成UNION 和FIND操作。

在UNION - FIND 算法设计中选择不同的数据结构和相应的优化策略,将直接影响算法执行的效率。

采用数组结构的算法设计与分析解决UNION - FIND问题可以选择最简单的数据结构——数组。

假设R是一个数组,用R[i]表示元素i所在的集合的名字。

开始时令每个元素所在的集合名字与元素名字相同,即,R[1 ] =1,R[ 2]=2,R[3] = 3,. . .... ,R[n] = n 。

执行FIND(i)操作非常方便,只需返回R[il的值即可。

执行n 条FIND操作只需O(n)时间。

但执行UNION(i,j、k )时,需要依次检查每个数组的下标变量的值,比较是否为i或j,若是i或j,则就将其下标变量改为k,即将R{i]和R[j]合并为R[k],如此执行一条UNION(i,j,k)操作就需要O(n)时间,从而执行n-1条UNION指令就需O(n)时间。

【转载】Union-Find算法详解

【转载】Union-Find算法详解

【转载】Union-Find算法详解【声明】本⽂转载⾃Union-Find 算法,也就是常说的并查集算法,主要是解决图论中「动态连通性」问题的。

问题描述简单说,动态连通性其实可以抽象成给⼀幅有 N 个结点的图连线,其中结点按 0~N 编号。

Union-Find 算法主要需要实现以下API:class UnionFind {public:/* 将 a 和 b 连接(连通) */void connect(int a, int b);/* 判断 a 和 b 是否连通 */bool isConnected(int a, int b);/* 返回图中的连通分量 */int getCount();}这⾥所说的「连通」是⼀种等价关系,也就是说具有如下三个性质:1、⾃反性:结点 a 和 a 是连通的。

2、对称性:如果结点 a 和 b 连通,那么 b 和 a 也连通。

3、传递性:如果结点 a 和 b 连通, b 和 c 连通,那么 a 和 c 也连通。

基本思路假定我们使⽤森林(若⼲棵树)来表⽰图的动态连通性,⽤数组来具体实现这个森林。

怎么⽤森林来表⽰连通性呢?我们设定树的每个节点有⼀个指针指向其⽗节点,如果是根节点的话,这个指针指向⾃⼰。

class UnionFind {public:UnionFind(int n);/* 将 a 和 b 连接(连通) */void connect(int a, int b);/* 判断 a 和 b 是否连通 */bool isConnected(int a, int b);/* 返回图中的连通分量 */int getCount() { return count; }private:/* 返回结点 x 的根节点 */int findRoot(int x);/** 其他函数 **/private:int count; // 连通分量vector<int> parent; // parent[i]: 结点 i 的⽗节点};如果某两个节点被连通,则让其中的(任意)⼀个节点的根节点接到另⼀个节点的根节点上:UnionFind::UnionFind(int n) : count(n) {// 初始时所有结点互不连通// ⽗节点指向⾃⼰for (int i = 0; i < n; ++i){parent.push_back(i);}}/* 判断 a 和 b 是否连通 */void UnionFind::connect(int a, int b) {int rootA = findRoot(a);int rootB = findRoot(b);if (rootA == rootB)return ;// 将⼀棵树接在另⼀棵树上parent[a] = rootB;// 连通分量 -1--count;}/* 判断 a 和 b 是否连通 */bool UnionFind::isConnected(int a, int b) {return findRoot(a) == findRoot(b);}/* 返回结点 x 的根节点 */int UnionFind::findRoot(int x) {// 根结点有 x == parent[x]while (x != parent[x])x = parent[x];return x;}我们发现,主要 API isConnected 和 connect 中的复杂度都是 findRoot 函数造成的,所以说它们的复杂度和 findRoot ⼀样。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

31
给出数组A[1…n],将其中的元素用如 下方法以非降序有效排序。 step 1.首先将A变换成堆,并使其具有 这样的性质,每个元素的键值是该元素本 身,即key(A[i])= A[i],1 ≤i ≤n。
32

step 2.由于A中各项的最大值存储在 A[1]中,可以将A[1] 和A[n]交换,使得 A[n]是数组中最大元素。 (排序:将数据依大小,顺序放在数组 中,最后一个是最大的,故交换A[1]和 A[n])

34
算法4.5 HEAPSORT 输入:n个元素的数组A[1…n] 输出:以非降序排列的数组A 1.MAKEHEAP(A) 2. for j n downto 2//j从n依次递减 3. 互换A[1] A[j] 4. SIFT-DOWN(A[1…j-1],1) 5. end for 定理4.2 算法HEAPSORT对n个元素排序 要用O(n log n)时间和Θ(1)的空间。
3
定义 4.1 一个(二叉)堆是一个几乎完全的 二叉树,它的每个节点都满足堆的特性: 如果v和p(v)分别是节点和它的父节点, 那么存储在p(v)中的数据项键值不小于存储 在v中数据项的键值。(大顶堆)
4
数据结构中堆的定义: 堆是满足下列性质的数列{r1, r2, …,rn}:
ri r2i (小顶堆) ri r2i 1
17
删除复杂度: 这些在算法DELETE中描述。有观察结论 3.4可知,堆树的高度是 log n ,所以从 一个大小为n的堆中删除一个元素所需要的 时间是O(log n)。
18
算法4.3 DELETEMAX//删除根节点 输入:堆H[1…n] 输出:返回最大值元素 x并将其从堆中删 除 1. x H[1] 2. DELETE(H,1) 3. Return x
,根据递推式求解:O(logn)
14
算法4.1 INSERT 输入:堆H[1…n]和元素x 输出:新的堆H[1…n+1], x为其元素之 一 1. n= n+1{增加H的大小} 2. H[n] x 3. SIFT-UP(H, n)

15
插入 (1)先将堆大小加1,然后将x添加到H 的末尾,再根据需要,把x上移,直到满足 堆特性, (2)观察结论3.4可知,如果n是新堆的 大小,那么堆树的高度是 log n ,所以将 一个元素插入大小为n的堆中所需要的时间 是O(log n)。
log n
n ) h 1 2
( 1)
29
数论上存在如下等式成立:
x kx 2 (1 x ) k 0
k

设x=1/2带入(1)的右边的和式
h 2 2 k 2 1 2 (1 ) h 0 2

1
O( n
h 0
log n
(k i)2
i 1
k 1
i
2 (k ) 2 (k 1) ... 2 (1) 2n
0 1
k 1
25
观察sift-down函数,每一次循环过程,包含有 两个if语句,故最多两次元素比较,故元素比较的总 次数上界是2*2n=4n; 观察下界:调用sift-down函数至少要执行一次 循环,所以元素的最小比较次数应为: 2*n/2>=n-1。 定理4.1 算法MAKEHEAP用来构造一个n个元素 的堆,令C(n)为执行该算法的元素比较次数,那 么n-1≤ C(n) <4n。因此,算法需要Θ(n) 时间和 Θ(1) 空间构造一个n元素的堆。
21
看另外一个堆建立方法: 在Θ(n)的时间内,用n个元素来建 立一个堆,下面给出这种方法的实现细 节。

22
(1) 将堆H[1…n]的树的节点以自顶向下、 从左到右的方式从1到n编码。
(2)把一棵n个节点的几乎完全的二叉树 转换成堆H[1…n]。 (3) 从最后一个节点开始(编码为n 的 那个)到根节点(编码为1的节点),逐 个扫描所有的节点,根据需要,每一次将 以当前节点为根节点的子树转换成堆。
10
过程:Sift-up算法 输入:数组H[1…n]和位于1和n之间的索引i 输出:上移H[i],以使它不大于父节点 1.done false 2.if i=1 then exit //节点i是根 3.repeat 4. if key(H[i])>key(H [i/2 ]) then 互换H[i]和H [i/2]//对比交换 5. Else done true 6. i i/2 7. Until i=1 or done
第四章
堆和不相交集数据结构
4.1 引言
4.2 堆 4.3 不相交集数据结构
1
4.2 堆
在许多算法中,需要支持下面两种运 算的数据结构:
插入元素和寻找最大元素。
支持这两种运算的数据结构称为优先队 列。 普通队列:那么寻找最大元素需要搜索整 个队列,开销较大;
2

排序数组: 那么插入运算就需要移动很多元素, 开销也较大. 优先队列的有效实现是使用一种称为 堆的简单数据结构。
24

算法复杂度分析: (1)设T是一个完全二叉树,高度是k=logn 。
(2)设A[j]对应第i层的第j个结点,则调用 sift-down函数需要调用的次数应该最多是 k-i(注:k是树的高度)(自底而上扫描连接 的数据)。 (3)根据二叉树的理论知:二叉树的第i层最 多有2i个结点,则循环的上界应该为:
11
Sift-down 算法: 前提: 当i≤n/2 , 设H[2i]和H[2i+1]中最大值为 max,如果H[i]中元素的键值变成了小于 max,这样就违反了堆的特性,树就不再 表示一个堆。 调整策略: 使H[i]渗到二叉树中适合它的位置上;
沿着这条路径的每一步,都将H[i]键值 和存储在它子节点中的两个键值里最大的 那个相比较。
n ) O (2 n ) O ( n ) h 1 2
30
4.2.3 堆排序 算法SELECTIONSORT(选择排序)的复 杂度: 用线性搜索来找最小值的时间需要用 Θ(n)的时间,算法就要用Θ(n2) 时间。 如何提高选择排序的复杂度呢,使用堆 是一个好的选择。
33
这时, A[1] 中的元素可能小于存放在 它的一个子节点中的元素,于是用过程 SIFT-DOWN将A[1…n-1]转换成堆。 (调整堆) 接下来将 A[1] 和A[n-1]交换,并调整 数组A[1…n-2]成为堆。交换元素和调整 堆的过程一直重复,直到堆的大小变成1 为止,这时A[1] 是最小的。
13
算法复杂度分析
Sift_down的复杂度分析: (1)以i结点和2i+1,2i+2的调整复杂度所 用时间为 (2)以2i+1或2i+2结点为父结点来调整的时间最坏是多 少? 发生调整的几率的树最多是2n/3,(即最底层恰好是 半满)。 则:
(1)
T (n) T (2n / 3) (1)
28
一个n元素的堆的高度是 ,在任意的高度h上,最多有结点数
log n
h 1 n / 2
设SIFT-DOWN()在h层上的作用时间是O(h),则 应该有一个上确界:
log n

h 0
n O(h) O(n h 1 2 h 0
19
删除最大值复杂度分析: (1) 在堆中返回最大键值元素需要Θ(1) 的时间,因为这个元素是树的根节点。 (2)修复堆需要一定的时间。 显然,这项运算的时间复杂性就是删除 运算的时间复杂性,即O(log n)。
20
4.2.2 创建堆 给出一个A[1…n],创建一个包含这些元 素的堆是容易的: 从空的堆开始,不断插入一个元素,直 到A完全被转移到堆中为止。 因为插入第j个键值用时O(log j),因此 用此方法创建堆栈的时间复杂性是O(n log n)。
8

元素H[j]的父节点如果不是根节点,则存 储在H[ j/2 ]中。
9
4.2.1 堆上的运算 下面是两个辅助运算 Sift-up 运算 : 前提:假定对于某个i>1, H[i]变成了键值 大于它父节点键值的元素,非堆结构。 处理方案:调整破坏堆的数据的位置; 实际方法: 沿着从H[i]的根节点的唯一一条路经, 把H[i]移到适合它的位置上。在沿着路径的 每一步上,都将H[i]键值和它父节点的键值 H[i/2 ]相比较。
见例4.3 创建堆的例子(手写)
23
算法MAKEHEAP构建了一个堆,其 数据项是存储在数组A[1…n]中的元素。 算法 4.4 MAKEHEAP 输入:n个元素的数组A[1…n] 输出: A[1…n]转换成堆 1. For i n/2 downto 1 2. SIFT-DOWN(A, i) 3. End for //注:叶子节点不需要处理,所以必须 从n/2开始处理,A[n/2+1], A[n/2+2],..A[n]对应于数的叶子节点

堆的特性蕴含着:沿着每条从根到叶子 的路径,元素的键值以非升序排列。 ( 大顶堆)
7
有n个节点的堆T(一棵几乎完全的二叉 树),可以有一个数组H[1…n]用下面的方 式来表示。
T的根节点存储在H[1]中。 假设T的节点x存储在H[j]中,如果它有 左子节点,这个子节点存储在H[2j]中;如 果它也有右子节点,这个子节点存储在 H[2j+1]中。
ri
r2i
例如:
36
65 81 73 55 40 49 34 12
r2i+1
27
98
是堆
6
对数据结构支持下面的运算: Delete-max[H]:删除最大键值的数据项 Insert[H,x]:插入项x到堆H中 Delete[H,i]:从堆H中删除第i项 Makeheap[A]:将数组A转换成堆
相关文档
最新文档