您的位置:首页 > 其它

并查集的学习笔记

2012-10-17 21:45 169 查看
今天主要是给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 );
}
}


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: