LA3027之并查集

在处理一些有N个元素的合并于查询问题时,我们经常会使用到并查集(union-find set)这样一种树形数据结构来处理,一般来说这样的问题都是看似很简单,但是数据量很大,容易超时,但是采用并查集这种数据结构却能很容易解决这些问题。

一般来说,并查集使用数组来实现,它实现的功能也很简单,主要涉及两个基本操作:

1.合并(union)两个不相交集合

2.判断两个元素是否属于同一集合(find).


基本操作:

1.union(x, y)

首先,初始化一个数组p[ ],其中p[ i ] = i,即每个节点都初始化为自己的父节点,union主要就是分别找到x和y所属集合的根节点root,将其中一个root指向另一个root,即p[root1] = p[root2].

2.find(x)

递归的方式找到x所在集合的根节点root 。

3.路径压缩

可以看出来,经过union实现的集合的结构可能会是一个只有一条链的树,那么这样的话每次find操作的复杂度都会达到O( n ),为了避免这种情况,便有了一种巧妙的方法,因为find操作时递归的实现,每次递归中找到root节点时就将所有子孙节点都指向该节点,这样的话以后每次find操作都将达到O(1)的复杂度,这就是路径压缩。

4.按秩合并

在union时,每次都将节点数较少的集合指向节点数多的集合,这样的话可以使得最终集合的高度降低,使查找更快。


例程:

int father[MAX]; /*father[x]表示x的父节点*/
int rank[MAX]; /*rank[x]表示x的秩*/

void init(void)
{
	for(int i=0; i<MAX; i++){
		father[x] = x; //根据实际情况指定的父节点可变化
		rank[x] = 0; //根据实际情况初始化秩也有所变化
	}
}

/* 查找x元素所在的集合,回溯时压缩路径*/
int find_set(int x)
{
	return father[x] != x ? father[x] = find_set(father[x]) : x;
}

/*按秩合并x,y所在的集合,按秩合并,实时更新秩。
*/
void Union(int x, int y)
{
	x = find_set(x);
	y = find_set(y);
	if (x == y) return;
	if(rank[x]>rank[y]){
		father[y] = x;
		rank[x] += rank[y];
	}
	else{
		father[x] = y;
		rank[y] += rank[x];
	}
}

并查集作为一种高效的数据结构有很多应用,例如:求无向图的连通分量个数,LCA问题,带限制的作业排序,求最小生成树等,下面这题LA3027便是利用并查集求出无向图的连通分量个数从而求解。

这个题目中,我们将每个元素看成一个顶点,而每个简单化合物就是一条边,当有K个顶点并有K条边时,说明这些边构成了一个环,即无向图的连通分量,所以我们只需要求出有多少个连通分量即可,使用并查集实现,代码如下:

#include <iostream>
#include <cstdio>
 
using namespace std;

int p[100001];
int sum;

int find_root(int x){
	return p[x] != x ? p[x] = find_root(p[x]) : x;
}

void union_set(int x, int y){
	x = find_root(x);
	y = find_root(y);
	if(x==y){
		sum++;
		//cout<<"#"<<sum<<endl;
	}
	else
		p[x] = y;
}
int main(){
	int x,y;
	while(scanf("%d",&x)==1){
		sum = 0;
		for(int i=0;i<100001;i++)
			p[i] = i;
		while(x!=-1){
			scanf("%d",&y);
			union_set(x,y);
			scanf("%d",&x);
		}
		printf("%d\n",sum);
	}	
	return 0;
} 



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章