初學並查集

現在,我是用一個初學者的眼光來寫並查集,因爲我還不會
並查集解決什麼問題:假如有一些點,你知道哪些點是直接相連的,但實際上間接相連也是相連,要你求有幾個連通分支(即分爲幾塊)。
哈哈,是不是有點懵,我舉個例子。假如有一個村,你知道其中一些人有親屬關係,求最多有幾個家族。比如你知道你和你爸爸有關係,又知到你爸爸和你姑姑有關係,又知道你姑姑和你媽媽有關係,那你們是不是就是一個家族,雖然沒有直接說你和媽媽有關係,但通過相連可以確定。而並查集就是查你分爲幾個家族,以及是否同時一個家族。

那怎麼做呢?你是不是想直接用二維數組保存你們的關係,那樣很麻煩的,用遍歷算法的時間複雜度太大了。那怎麼辦,你可以形成一個樹形結構,只要根節點相同就可以了就證明在同一個連通分支中。 就是說比如你們家族的代言人爲你媽媽,然後看你和一個人的關係就看你們的代言人是不是同一個。當然要分層。
設立一個數組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);
	}
	
} 

好的,大家要自己也實現一下,謝謝大家。

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