并查集

定义:

并查集是一种维护集合的数据结构,他的名字中,“并”,“查”,“集”分别取自合并,查找,集合。所有说,并查集支持下面两个操作:

  1. 合并:合并两个集合。
  2. 查找:判断两个元素是否在一个集合。

那么我们用什么实现并查集?
答案:数组。 int father[N];
其中,father[i]表示元素i的父亲节点。
例如:
并查集_第1张图片
如果father[i]=i,则说明元素i是该集合的根节点,但是对于同一个集合来说,只存在一个根节点,并且将这个点作为这个集合的标志(意思就是,找到一个father[i]=i的点就说明找到了一个集合)!

操作

1.初始化

一开始,每个元素都是一个独立的集合,因此,需要令所有的father[i]=i;

for(int i=1;i<=n;i++) {
		father[i]=i;
	}

2.查找

由于一个集合只能有一个根节点,所以,查找的过程就是对于给定的节点查找其根节点的过程。可以使用递归或者递推,反复寻找父亲节点,直到找到根节点(即father[i]==i)

int findfa(int x){
	while(x!=father[x]){
		x=father[x];
	}
	return x;
}

3.合并

合并是指把两个集合合并成一个集合,一般题目中会把已知的,同一个集合的元素告诉你,然后把这两个同一个集合的元素合并在一个集合里面,合并过程一般是把一个集合的根节点的父亲指向另一个集合的根节点。
步骤:

  1. 先把给定的a,b两个元素判断是否已经在一个集合里面了(用findfa(a)==findfa(b)判断)。
  2. 如果已经是同一个集合的,则继续下两个元素的判断,否则将两个集合合并,可以令father[findfa(a)]=findfa(b);也可以反过来,规则自己决定。

注意:
在这里,不能直接把其中一个元素的父亲设置为另一个元素,即直接令father[a]=b;这样并不能把a 的整个集合与b的整个集合进行合并,只是单单把a归类于了b,但是如果a是存在一个不等于本身的根节点时,其根节点并没有得到合并!

结论:

并查集产生的每一个集合都是一棵树。

路径压缩:

即把下图中左边的树变成右边的样子,因为左边的树的样子入要找的元素是最后一个的根节点,则需要进行多次的回溯,而右边的查找时间复杂度则为O(1).
并查集_第2张图片
所以,我们进行压缩。主要是在findfa函数进行改造,在查找一个数字的根节点的时候,把途中经过的所有点的父节点都改为根节点。
步骤:

  1. 按照远些的写法获得x的根节点r,
  2. 重新从x开始走一遍寻找根节点的过程,把路径上经过的所有节点的父亲全部改为根节点r.
int findfa(int x){
	int s=x;   //存储开始节点,因为后面x在循环中一直改变, 
	while(x!=father[x]){
		x=father[x];
	}
	//此时的x变成了s节点的根节点
	while(father[s]!=x){
		father[s]=x;
		s=father[s];
	} 
	return x;
}

完整代码:

/**
L2-024 部落 (25分)
在一个社区里,每个人都有自己的小圈子,还可能同时属于很多不同的朋友圈。我们认为朋友的朋友都算在一个部落里,
于是要请你统计一下,在一个给定社区中,到底有多少个互不相交的部落?并且检查任意两个人是否属于同一个部落。

输入格式:
输入在第一行给出一个正整数N(N≤10的4次方),是已知小圈子的个数。随后N行,每行按下列格式给出一个小圈子里的人:
K P[1] P[2]....P[K]
其中K是小圈子里的人数,P[i](i=1,?,K)是小圈子里每个人的编号。这里所有人的编号从1开始连续编号,最大编号不会超过10的4次方. 
之后一行给出一个非负整数Q(Q≤10),是查询次数。随后Q行,每行给出一对被查询的人的编号。

输出格式:
首先在一行中输出这个社区的总人数、以及互不相交的部落的个数。随后对每一次查询,如果他们属于同一个部落,则在一行中输出Y,否则输出N。

输入样例:
4
3 10 1 2
2 3 4
4 1 5 7 8
3 9 6 4
2
10 5
3 7
 
输出样例:
10 2
Y
N
**/
#include
using namespace std;
int father[10003];
int n,k,a,b,ma=-1;
int findfa(int x){
	int s=x;   //存储开始节点,因为后面x在循环中一直改变, 
	while(x!=father[x]){
		x=father[x];
	}
	//此时的x变成了s节点的根节点
	while(father[s]!=x){
		father[s]=x;
		s=father[s];
	} 
	return x;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=10000;i++){
		father[i]=i;      //初始化father 
	} 
	for(int i=1;i<=n;i++) {
		scanf("%d",&k);
		scanf("%d",&a);
		if(a>ma)  ma=a;
		int temp=findfa(a);
		for(int j=1;j<k;j++){
			scanf("%d",&b);
			int tt=findfa(b);
			if(temp!=tt)   {   //father不同 
				father[tt]=temp; 
			}
			if(b>ma)  ma=b;			
		}	
	}
	int count=0;
	for(int i=1;i<=ma;i++){
		if(father[i]==i) count++;
	} 
	printf("%d %d\n",ma,count);
	int c;
	int x,y;
	scanf("%d",&c);
	for(int i=1;i<=c;i++){
		scanf("%d %d",&x,&y);
		if(findfa(x)==findfa(y))  printf("Y\n");
		else     printf("N\n");
	}
	
	
} 

你可能感兴趣的:(#,神奇的树)