数据结构与算法分析(四)——不相交集

  • 基本介绍

一个集合S,集合中一个元素a。a的等价类是S的一个子集,该子集包含所有与a有关系的元素。

等价类形成是对S的一个划分且S中的每一个成员恰好出现在一个等价类中。这样,判断a与b是否有关系,

只需要判断a与b是否在一个等价类中即可。

对于集合S划分,取任意两个等价类,Si与Sj,如果Si∩Sj = ∅,则称这些集合不相交。

对于不相交集,有两种操作,Union/Find操作。Find操作找包含给定元素的集合(等价类)名字。

Union把两个等价类合并成一个新的等价类。

  • 数据结构:采用树来表示每一个集合(等价类),因为树上的元素都有一个共同的根。

Union(X, Y),将X,Y合并成一个新的等价类,且X做为根。

Union(5,6)

Union(7,8)

Union(5,7)

这样的构造方法,最坏情况下可以构建一课高度为N-1的树,即Union(7,8), Union(6,7), Union(5,6)……

这使得Find操作在N-1次操作下才能找到树根,运行时间O(N)

将上述森林用数组表示,约定数组1-8个元素对应的值代表其父亲,例如元素8对应的值为7,表示其父亲为7。

而5对应的值为0,表示5本身就是树根。

这样找树根就是一个递归过程,如:Find(8), 父亲为7, 执行Find(7),父亲为5,执行Find(5),对应值为0,表示5为树根,递归结束,

这样即找到8所在等价类的树根为5。判断6,8是否有关系,即Find(8) == Find(6)是否成立。

 1 typedef int SetType;
 2 typedef int ElementType;
 3 typedef int* DisjSet;
 4
 5 DisjSet Initialize(int Num)
 6 {
 7     DisjSet S = new SetType[Num + 1];
 8
 9     for(int i = 0; i < Num + 1; i++)
10         S[i] = 0;
11     return S;
12 }
13
14 void Destroy(DisjSet S)
15 {
16     delete [] S;
17 }
18
19 void Union(DisjSet S, SetType root1, SetType root2)
20 {
21     S[root2] = root1;
22 }
23
24 SetType Find(DisjSet S, ElementType X)
25 {
26     if(S[X] <= 0)
27         return X;
28     else
29         return Find(S, S[X]);
30 }
31
32 int main()
33 {
34     int elementNum = 8;
35     DisjSet S = Initialize(elementNum);
36
37     Union(S, 5, 6);
38     Union(S, 7, 8);
39     Union(S, 5, 7);
40
41     cout << ( Find(S, 4) == Find(S, 5) )<< endl;
42     cout << ( Find(S, 6) == Find(S, 8) )<< endl;
43
44     Destroy(S);
45     system("pause");
46     return 0;
47 }  

  • 优化Union

为了避免树的深度过大,可以每次让深度大的树做为新根,这样减少树深度增加速率。

那么就需要记住当前根的深度,而由于我们只采用了一个数组,所以,可以让根的值为负值,代表深度。

这样,根节点的值为-1,表示深度为-1。

对于上述情形,执行Union(4,5)。按照之前的Union操作,得到结果为:

优化后结果

对应优化后结果的数组如下:

 1 void Union(DisjSet S, SetType root1, SetType root2)
 2 {
 3     if(S[root2] < S[root1])
 4         S[root1] = root2;
 5     else
 6     {
 7         if(S[root1] == S[root2])
 8             S[root1]--;
 9         S[root2] = root1;
10     }
11 }  

  • 路径压缩

对于比较深的树,Find操作还是比较耗时的,要一步一步递归直至树根。

改进思路:执行一次Find后,让该路径上,所有节点直接指向根,而不需要指向父亲,这样下一次查找的时候能够快速找到根,节约时间。

执行一次Find(8)操作。由于元素8所在树的树根为5,所以执行Find后,数组要变成。

这样,当进行下一次调用Find(8)的时候,能够快速找到8对应的树根。

1 SetType Find(DisjSet S, ElementType X)
2 {
3     if(S[X] <= 0)
4         return X;
5     else
6         return S[X] = Find(S, S[X]);
7 }  

转自:http://blog.csdn.net/spch2008/article/details/9338943

时间: 2024-10-13 22:28:25

数据结构与算法分析(四)——不相交集的相关文章

《数据结构与算法分析:C语言描述》复习——第四章“树”——AVL树

2014.06.15 16:22 简介: AVL树是一种高度平衡的二叉搜索树,其命名源自于联合发明算法的三位科学家的名字的首字母.此处“平衡”的定义是:任意节点的左右子树的高度相差不超过1.有了这个平衡的性质,使得AVL树的高度H总是接近log(N),因此各种增删改查的操作的复杂度能够保证在对数级别.没有bad case是AVL树与普通的二叉搜索树的最大区别.为了实现平衡性质,我们需要记录每个节点的高度(或者平衡因子)来检测不平衡的情况.为了修正高度不平衡,需要用到“旋转”的方法,分为单旋转和双

[数据结构与算法分析(Mark Allen Weiss)]不相交集 @ Python

最简单的不相交集的实现,来自MAW的<数据结构与算法分析>. 代码: class DisjSet: def __init__(self, NumSets): self.S = [0 for i in range(NumSets+1)] def SetUnion(self, S, Root1, Root2): S[Root2] = Root1 def Find(self, X, S): if S[X] <= 0: return X else: return self.Find(S[X],

《数据结构与算法分析》学习笔记(四)——栈ADT

一.栈ADT是what? 1.定义 栈,是限制插入和删除都只能在一个位置上进行的表. 2.图示 3.栈的基本功能 (1)是否为空 (2)进栈 (3)出栈 (4)清空 (5)取栈顶 二.栈的链表实现 <数据结构与算法分析>学习笔记(四)--栈ADT

《数据结构与算法分析》学习笔记(二)——算法分析

一.对算法分析方法的最简单的理解和使用方法 1.首先大家可能一般会被那些数学的概念搞晕,其实简单理解下来,就是假设任何语句执行的效率都是一样的,所以设定每一个语句的执行时间都是一个时间单位,那么只要计算这个程序到底执行了多少语句,就可以算出其时间复杂度. 2.其次就是我们要明白,我们是个估算,所以可以进行化简,明显我们可以忽略那些相对来说低阶的项,只分洗最高阶项.然后主要就是有这些常见的法则: (1)FOR循环 一次for循环的运行时间至多是该for循环内语句的运行时间乘以迭代次数. (2)嵌套

《数据结构与算法分析—C语言描述》pdf

下载地址:网盘下载 内容简介 编辑 <数据结构与算法分析:C语言描述(原书第2版)>内容简介:书中详细介绍了当前流行的论题和新的变化,讨论了算法设计技巧,并在研究算法的性能.效率以及对运行时间分析的基础上考查了一些高级数据结构,从历史的角度和近年的进展对数据结构的活跃领域进行了简要的概括.由于<数据结构与算法分析:C语言描述(原书第2版)>选材新颖,方法实用,题例丰富,取舍得当.<数据结构与算法分析:C语言描述(原书第2版)>的目的是培养学生良好的程序设计技巧和熟练的算

&lt;数据结构与算法分析 C++描述&gt;算法分析之最大子序列和问题

声明:这个系列博客是<数据结构与算法分析 C++描述>的读书笔记系列 参考博客:点击打开链接 本文是原书第二章内容,主要内容包括:算法的时间复杂度分析/算法的优化,分析的例子是很出名的最大子序列求和问题. 分为了四种方法来求解:穷举/穷举优化/递归(分治)/联机算法(动态规划), 算法复杂度为O(N^3)/O(N^2)/O(N*logN)/O(N). 思路都在具体代码里 ----------------------------------------代码如下------------------

数据结构与算法分析之简单排序算法

在排序算法中,简单排序主要有三种,分别为冒泡排序.选择排序.插入排序,学习理解好这三种排序算法有助于进一步研究数据结构与算法分析.下面,简单地谈一谈冒泡排序.选择排序.插入排序的原理及区别. 冒泡排序原理: 1.比较相邻的元素.如果前一个比后一个大,它们就交换. 2.每对元素都要进行同样的动作,从后往前比较. 3.每趟都会确定一个位置的元素,因此n个元素,需要n-1趟才能确定各个元素的位置. 例如:对23,4,56,11四个数进行冒泡排序. 第一趟 4,23,11,56 第二趟 4,11,23,

数据结构与算法分析-AVL树深入探讨

.title { text-align: center; margin-bottom: .2em } .subtitle { text-align: center; font-size: medium; font-weight: bold; margin-top: 0 } .todo { font-family: monospace; color: red } .done { font-family: monospace; color: green } .priority { font-fami

学习记录:数据结构与算法分析c++版

数据结构与算法分析c++版 学习记录 一.绪论 1.数据结构的必要性 计算机程序被设计出来的目的不仅仅是为了计算,同时其也要完成数据的提取和检索任务,并尽可能地高效快速.在这个意义下,数据结构和算法分析作为程序的核心,就显得尤为重要.如何利用数据结构和算法,设计出简单易懂,并且高效地利用计算机资源的程序是这门课的核心议题. Def    一个算法被称为有效的(effective),如果其能在计算机的资源限制下解决相应问题:这些限制通常包括计算机储存量限制,以及算法运行的时间限制.    算法的消

数据结构1:数据结构和算法分析

问题引出 假设有一道题目:有一组N个数而要确定其中第k个最大者,我们称之为选择问题,那么这个程序如何编写?最直观地,至少有两种思路: 1.将N个数读入一个数组中,再通过某种简单的算法,比如冒泡排序法,以递减顺序将数组排序,则第k个位置上的元素就是我们需要的元素 2.稍微好一些的做法,将k个元素读入数组并以递减顺序排序,接着将接下来的元素再逐个读入,当新元素被读到时,如果它小于数组中的第k个元素则忽略之,否则将其放到数组中正确的位置上,同时将数组中的一个元素挤出数组,当算法终止时,位于第k个位置上