初学并查集

现在,我是用一个初学者的眼光来写并查集,因为我还不会
并查集解决什么问题:假如有一些点,你知道哪些点是直接相连的,但实际上间接相连也是相连,要你求有几个连通分支(即分为几块)。
哈哈,是不是有点懵,我举个例子。假如有一个村,你知道其中一些人有亲属关系,求最多有几个家族。比如你知道你和你爸爸有关系,又知到你爸爸和你姑姑有关系,又知道你姑姑和你妈妈有关系,那你们是不是就是一个家族,虽然没有直接说你和妈妈有关系,但通过相连可以确定。而并查集就是查你分为几个家族,以及是否同时一个家族。

那怎么做呢?你是不是想直接用二维数组保存你们的关系,那样很麻烦的,用遍历算法的时间复杂度太大了。那怎么办,你可以形成一个树形结构,只要根节点相同就可以了就证明在同一个连通分支中。 就是说比如你们家族的代言人为你妈妈,然后看你和一个人的关系就看你们的代言人是不是同一个。当然要分层。
设立一个数组int pre[50010],这个数组记录了他的父亲节点是谁。如pre[100]=10代表了100的父节点是10,当一个数的父节点是它自己的时候,证明了这个数就是根节点。
下面这个就是查找根节点的函数

int find(int x)
{
	int r=x;
	while(pre[r]!=r){
		r=pre[r];
	}
	return r;
}

那实际上,怎么合并一块一块呢?假如有一个a与b相连,怎么表示他们的关系呢?实际上,就是令其中一个的根节点的父节点变为另外一个的根节点就好了。

void join(int x,int y){
	int fx=find(x),fy=find(y);
	if(fx!=fy)
	pre[fx]=fy;
}

然后,这样是不是有可能变成等级链太长了,难以查找。所以要路径压缩。什么意思?就是说如果是所有父节点就是根节点最好了。有点晚了,以下就不细讲了,直接看实现。
初始化

int pre[max];     //集合index的类别,或者用parent表示 
int rank[max];    //集合index的层次,通常初始化为0 
int data[max];    //计划index的数据结构类型 
//初始化集合 
void Make_pre(int i)
{
	pre[i]=i;     //一个集合的pre都是这个集合自己的标号。没有跟它同类的集合,那么这个集合的源头只能是自己了。 
	rank[i]=1;    //树的高度
}

查找父节点

int find(int x)
{
	int r=x;
	while(pre[r]!=r){       //当父节点是自己时就是根节点
		r=pre[r];           //一直往上找
	}
	return r;
}

接下来是合并,怎么合并更好,就是让较小的树的根节点的父节点变为较大树。

void Union(int i,int j){
	i=find(i);
	j=find(j);
	if(i==j)return ;     //根节点相同不做处理 
	if(rank[i]>rank[j])pre[j]=i;
	else
	{
		if(rank[i]==rank[j])rank[j]++;
		pre[i]=j;         
	}
}

综合代码就不打了,给一道例题吧。
———————————————————————————
———————————————————————————
There are so many different religions in the world today that it is difficult to keep track of them all. You are interested in finding out how many different religions students in your university believe in.

You know that there are n students in your university (0 < n <= 50000). It is infeasible for you to ask every student their religious beliefs. Furthermore, many students are not comfortable expressing their beliefs. One way to avoid these problems is to ask m (0 <= m <= n(n-1)/2) pairs of students and ask them whether they believe in the same religion (e.g. they may know if they both attend the same church). From this data, you may not know what each person believes in, but you can get an idea of the upper bound of how many different religions can be possibly represented on campus. You may assume that each student subscribes to at most one religion.
Input
The input consists of a number of cases. Each case starts with a line specifying the integers n and m. The next m lines each consists of two integers i and j, specifying that students i and j believe in the same religion. The students are numbered 1 to n. The end of input is specified by a line in which n = m = 0.
Output
For each test case, print on a single line the case number (starting with 1) followed by the maximum number of different religions that the students in the university believe in.
Sample Input
10 9
1 2
1 3
1 4
1 5
1 6
1 7
1 8
1 9
1 10
10 4
2 3
4 5
4 8
5 8
0 0
Sample Output
Case 1: 1
Case 2: 7
Hint
Huge input, scanf is recommended.
——————————————————————
——————————————————————
POJ - 2524,自行有道。

#include<stdio.h>
#include<string.h>
long long pre[50010];
long long rank[50010];
void Init(long long n){
	long long i;
	for(i=0;i<n;i++){
		pre[i]=i;         //自己为根节点 
		rank[i]=1;        //高度都为1 
	}
}
long long find(long long x){    //找祖先 
	long long i=x;
	while(pre[i]!=i)
	i=pre[i];
	return i;
}
long long combine(long long a,long long b){
	long long i=find(a),j=find(b);
	if(i==j)return 0;
	if(rank[i]<rank[j])pre[i]=j;      //小树祖先变大树 
	else{
		if(rank[i]==rank[j]){
			rank[i]++;
		}
		pre[j]=i;
	} 
}
int main()
{
	long long n,m,i,j,casenu=0,a,b,sum=0;
	scanf("%lld %lld",&n,&m);
	while(1){
		sum=0;
		if(n==0&&m==0)break;
		Init(n);
		for(i=0;i<m;i++){
			scanf("%lld %lld",&a,&b);
			combine(a,b);
		}
		for(i=0;i<n;i++){
			if(pre[i]==i)sum++;
		}
		printf("Case %lld: ",++casenu);
		printf("%lld\n",sum);
		scanf("%lld %lld",&n,&m);
	}
	
} 

好的,大家要自己也实现一下,谢谢大家。

发布了6 篇原创文章 · 获赞 8 · 访问量 145

猜你喜欢

转载自blog.csdn.net/weixin_45981189/article/details/104872204