用于不相交集合的数据结构
总结:这一章讲了并查集的相关概念,以及主要的MAKE-SET, UNION, FIND-SET操作,并给出了并查集的链表表示和森林表示方式。
1. 不相交集合上的操作
不相交集合数据结构保持一组不相交的动态集合,每个集合通过一个代表来标识,代表即集合中的某个成员。
一些操作:
MAKE-SET(x): 建立一个新的集合,其唯一成员为x。
UNION(x,y): 将包含x和y的动态集合合并为一个新的集合。
FIND-SET(x): 返回一个指针,指向包含x的集合的代表。
应用:例如,确定一个无向图中连通子图的个数。
2. 不相交集合的链表表示
每一个集合用一个链表表示。每个链表中的第一个对象作为它所在集合的代表。
每一个对象的结构:
1)集合成员
2)指向包含下一个集合成员的对象的指针
3)指向代表的指针
每个链表都包含head指针和tail指针,head指向链表的代表,tail指向链表中最后的对象。
MAKE-SET(x): O(1),创建新链表,其仅有对象为x
FIND-SET(x): O(1),返回x指向代表的指针
UNION(x,y): 将x所在的链表拼接到y所在链表的表尾。注意,对于原先x所在链表中的每一个对象,都需要更新其指向代表的指针。
加权合并启发式策略:设每个表还包括了表的长度,合并时,总是把较短的表拼到较长的表上。
使用加权合并策略,对m个MAKE-SET, UNION和FIND-SET操作所构成的序列(其中n个MAKE-SET操作,因此UNION操作的次数至多为n-1),花费的总时间为O(m+nlgn)。
3. 不相交集合森林
利用有根树来表示集合,每棵树表示一个集合。树中的每个成员仅指向其父节点,树的根的父节点仍是自己,且树的根即集合的代表。
启发式策略:
1)按秩合并
合并时,使包含较少结点的树的根指向包含较多结点的树的根。秩,结点高度的上界。因此,即,具有较小秩的根在UNION操作中要指向具有较大秩的根。
2)路径压缩
在FIND-SET操作中,使查找路径上的每个结点都直接指向根节点。
设rank[x]表示结点的秩,即x的高度的上界,p[x]表示x的父节点
伪代码
MAKE-SET(x)
p[x] <- x
rank[x] <- 0
伪代码
UNION(x,y)
LINK(FIND-SET(x),FIND-SET(y))
伪代码
LINK(x,y)
if rank[x] > rank[y]
then p[y] <- x
else p[x] <- y
if rank[x]=rank[y]
then rank[y] <- rank[y]+1
伪代码
FIND-SET(x)
if x!=p[x]
then p[x] <- FIND-SET(p[x])
return p[x]
FIND-SET采用了两趟方法:一趟沿查找路径上升,直至找到根;第二趟沿查找路径下降,以便更新每个结点,使之直接指向根。
分析:当同时采用按秩合并和路径压缩时,对m个MAKE-SET, UNION, FIND-SET的操作序列,总的运行时间可看作与m成线性关系。
附录:
- typedef struct _node
- {
- _node* parent;
- int rank;
- }node;
- node *s[5000];
- void makeSet(int x)
- {
- s[x]=new node;
- s[x]->rank=0;
- s[x]->parent=s[x];
- }
- node* findSet(node* s)
- {
- if(s!=s->parent)
- {
- s->parent=findSet(s->parent);
- }
- return s->parent;
- }
- void link(node *s1, node *s2)
- {
- if(s1==s2)
- return;
- if(s1->rank > s2->rank)
- s2->parent=s1;
- else
- {
- s1->parent=s2;
- if(s1->rank==s2->rank)
- s2->rank++;
- }
- }
- void _union(node *s1, node *s2)
- {
- link(findSet(s1),findSet(s2));
- }