并查集基础
昨天用了一天时间学了并查集,在网上找了好多资料,也看了好多人写的,还在poj上刷了几道水题,才终于搞懂了并查集的一些基础内容,所以就做一些总结。在学习过程中刷了几道题,也做一点总结。
其实之前的数据结构课就讲过union-find算法,当时竟然没有察觉这就是并查集o(╯□╰)o!
先上定义: 并查集是一种树型数据结构,用于处理一些不相交集合(Disjoint Sets)的合并以及查询问题。常常在使用在以森林来表示。(Wiki)
(其实算法导论上就用了名为不相交集合数据结构的名字作为并查集那一部分的标题,当时看着隐隐有些蛋疼)
并查集的重点主要在于2部分:
1.查找元素所在森林的根节点(Find)。分布查找两元素,然后就可以判断它们是否属于一个集合。
在查找中,有个优化的技术点是路径压缩。这个等会结合代码讲。
2.合并两个不相交集合(Union)。
在合并中,也有个优化的技术点是按秩合并。这个也等会讲。
下面结合例子和代码来了解并查集。
例 犯罪团伙
void Make_Set(int x) { father[x]=x; }
查找函数: Find_Set(int x): 查找一个元素所属集合,找出它的根节点即可,这是这个集合的代表元
int Find_Set(int x) { if(x!=father[x]) return Find_Set(father[x]); else return x
合并函数: Union(int x,int y) 合并x,y所在的两个集合
void Union(int x,int y) { x=Find_Set(x); y=Find_Set(y); if( x==y ) return; else father[x]=y; }
示意图:
以上并查集的基本部分就大概说完了,然后就可以讨论一下并查集的两个优化
1. 在查找的时候用的路径压缩
从上面Find_Set代码中,我们知道查找使用的是递归,当元素排成一条链这样的极端情况时,查找可能就会很费时(O(n)). 所以我们可以再递归时就将所有这条路径上的节点全部直接指向跟节点,这样就可以压缩路径长度。
int Find_Set(int x) { if(x!=father[x]) return father[x]=Find_Set(father[x]); else return x; }
2. 在合并时用的按秩合并
上面在合并的时候,我们直接就将一个节点的父节点给接到了另一个的下面,这样可能会带来树过高,这里可以采用按秩合并的方法,这里的秩实际上可以理解为一个树的高度。
使用秩合并还需要定义一个rank[x]
在Make_Set(x)中增加一句 rank[x]=0;//所有初始高度都是0。
然后修改Union函数
void Union(int x, int y) { x=Find_Set(x); y=Find_Set(y); if(x==y) return; else{ if(rank[x]>rank[y]) father[y]=x; else{ father[x]=y; if(rank[x]==rank[y]) //当两个高度相同时,合并会使得y树高度增加1 rank[y]++; } } }
到此,并查集的基本内容就是那么多了。昨天做的题目是:3道普通并查集,1道种类并查集
poj 1611 poj 2524 poj2236; poj1182
POJ-1611
、
POJ-2524
、
POJ-2236
前两道都是裸的并查集,比较简单、第3道加了一点计算几何。第4道是著名的食物链,很有难度。