概述
并查集是一种树型的数据结构,常用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。
种类
链表中的每个元素设两个指针:一个指向同一集合中的下一个元素;另一个指向表首元素。
1. 链结构的并查集
采用链式存储结构,在进行集合查找时的算法复杂度仅为O(1);但合并集合时的算法复杂度却达到了O(n)。如果我们希望两种基本操作的时间效率都比较高的话,链式存储方式就“力不从心”了。
2.树结构的并查集
采用树结构支持并查集的计算能够满足我们的要求。并查集与一般的树结构不同,每个顶点纪录的不是它的子结点,而是将它的父结点记录下来。(往上记录)下面是树结构的并查集的两种运算方式
⑴直接在树中查询
⑵边查询边“路径压缩”
对应与前面的链式存储结构,树状结构的优势非常明显:编程复杂度低;时间效率高。
基本操作
并查集是一种非常简单的数据结构,它主要涉及两个基本操作,分别为:
- 合并两个不相交集合
- 判断两个元素是否属于同一个集合
(1)合并两个不相交集合(Union(x,y))
合并操作很简单:先设置一个数组Father[x],表示x的“父亲”的编号。那么,合并两个不相交集合的方法就是,找到其中一个集合最父亲的父亲(也就是最久远的祖先),将另外一个集合的最久远的祖先的父亲指向它。
上图为两个不相交集合,b图为合并后Father(b):=Father(g)
(2)判断两个元素是否属于同一集合(Find_Set(x))
本操作可转换为寻找两个元素的最久远祖先是否相同。可以采用递归实现。
优化
直接在树中查询
集合的合并算法很简单,只要将两棵树的根结点相连即可,这步操作只要O(1)时间复杂度。算法的时间效率取决于集合查找的快慢。而集合的查找效率与树的深度呈线性关系。因此直接查询所需要的时间复杂度平均为O(logN)。但在最坏情况下,树退化成为一条链,使得每一次查询的算法复杂度为O(N)。
边查询边“路径压缩
寻找祖先时,我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次Find_Set(x)都是O(n)的复杂度。为了避免这种情况,我们需对路径进行压缩,即当我们经过”递推”找到祖先节点后,”回溯”的时候顺便将它的子孙节点都直接指向祖先,这样以后再次Find_Set(x)时复杂度就变成O(1)了,如下图所示。可见,路径压缩方便了以后的查找。
想法很简单:在集合的查找过程中顺便将树的深度降低。采用路径压缩后,每一次查询所用的时间复杂度为增长极为缓慢的ackerman函数的反函数——α(x)。对于可以想象到的n,α(n)都是在5之内的。
Union(x,y)时,按秩合并
即合并的时候将元素少的集合合并到元素多的集合中,这样合并之后树的高度会相对较小。