今天主要是给MTS做了一个准备,重点学习了并查集。
什么是并查集?
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。(摘自百度百科)
并查集中,每个集合里的元素一般都是有某种关系的,比如连接关系:假设最初a, b 和 c 都是独立的点,后来a 与 b 相连,b又 与 c 相连,那么我们说a 可以到 c,相当于a 与 c相连,我们可以把a, b 和c放到一个集合里去,也就是将三个集合合并;再假设有,a 和 b组成集合A, c和d 组成集合B,如果在实际的应用中, a 和c是一组,我们要把它们以及与它们想关联的点放到一个集合中,那么我就要查询a 和 c 都分别属于哪一个集合,然后才知道把哪两个集合合并。
用森林来表示并查集的时候,每一个集合的名称就是相应森林的根。所以,在此我们表示并查集的时候,可以把每个点都与根相连,用数组father来存储一个点的前驱,这个前驱经常是相应的根。
一般情况下,我们会初始化并查集(这里就是指用森林来表示的时候),将每个结点都认为是独立的,是一个森林,根就是这个结点本身。
然后,根据数据情况,进行查询与合并:这里查询的主要目的是查询这个点应该属于哪个森林,也就是查询它所属森林的根,这里可以用递归来查询;合并,就是将两个集合的根相连,也就是使得其中一个集合的根成为另一个集合的祖先,“father[ B的根 ] = A的根”, 那么B的根的前驱就是A的根。
在这里,我们用father【】来表示一个节点前驱,findFather( ) 这函数(我们简称F)是用来查找集合的根,并且更新点的前驱,使得father【i】中存储的数据是它所在集合的根,这样为之后查询提供方便。那么合并我们用函数Union()(我们之后简称U)来实现,U中也有对father【】的更新,但是值得区别的是,在U中的更新主要是为了合并,如果不合并,那么这个点或者这个集合就不会被加入另一个集合中了;但是,在F中,对于father【x】的更新改变了x的前驱,使得x的前驱就是集合的根,如果不更新,这个x点也是和这个集合中其他点相连,在这个集合内的,更新之后可以便于之后的查找。
另外,还要注意的是,尽管我们在每次调用F的时候,都会对相应的点的前驱进行调整,但是还是不能时刻保证father【】中的值每时每刻都是该点所在集合的根,原因是每一次你去找合并两个集合的时候,只是做了两个操作,第一是找出两个集合的根,第二是把集合连起来,在你找到根的时候,你只是把原来集合中的点的前驱都改成了未合并之前的集合的根,但是在做了第二操作——合并之后,新的集合的根是原来两个集合中的一个,这就说明这新的集合中有一些点(属于原来两个集合中的一个)的前驱还是未合并时它们所属集合的根。因此要注意的是,在查找根的时候的还是要用F来做,不要混淆father与F的作用(father用来存储前驱结点)。
HUD的1232畅通工程, 是就一个典型的用并查集来解决的问题。
题目:给定一个无向图,求需要至少再加多少条边才能使所有的点连通。
分析:那么应用上述提及的并查集,就是把连在一起的点当做是一个集合,建立“森林”后,看有多少个集合,集合数减1就是要曾加的边的数目。
代码如下:
#include <cstdio> #include <cstring> int father[1010]; bool flag[1010]; int n, m, s, e; void init() { for ( int i = 1; i <= n; ++i ) father[i] = i; memset(flag, false, sizeof(flag) ); } int findFather( int x ) { if ( x != father[x] ) father[x] = findFather( father[x] ); return father[x]; } void Union( int a, int b ) { int x = findFather(a); int y = findFather(b); if ( x == y ) return; father[y] = x; } int main() { while ( scanf("%d", &n) && n ) { scanf("%d", &m); init( ); for ( int i = 1; i <= m; ++i ) { scanf("%d%d", &s, &e); Union( s, e ); } //for ( int i = 1; i <= n; ++i ) printf( "%d %d\n", i, father[i]); for ( int i = 1; i <= n; ++i ) { flag[findFather(i)] = true; } int ans = 0; for ( int i = 1; i <= n; ++i ) if ( flag[i] ) ++ans; printf("%d\n", ans - 1 ); } }