並查集的分析及應用

iaccepted發表於2014-01-27

昨天看了幾篇有關並查集的論文,對理解並查集很有幫助,在這裡寫篇部落格來記錄下自己對並查集的簡單理解,以及並查集的簡單運用。


並查集的分析


並查集的概念主要是處理集合問題(或可抽象成集合概念的問題),首先數學上的集合概念做個初步的分析

首先,我們從數學的角度給出等價關係和等價類的定義:
定義1:如果集合S中的關係R是自反的,對稱的,傳遞的,則稱他為一個等價關係。
——自反:x=x;
——對稱:若x=y,則y=x;
——傳遞:若x=y、y=z,則x=z。?要求:x、y、z必須要同一個子集中。
定義2:如果R是集合S的等價關係。對於任何x∈S,由[x]R={y|y∈S and xRy}給出的集合[x]RS
稱為由x∈S生成的一個R的等價類。
定義3:若R是集合S上的一個等價關係,則由這個等價關係可產生這個集合的唯一劃分。
即可以按R將S劃分為若干不相交的子集S1,S2,S3,S4,…,他們的並即為S,則這
些子集Si變稱為S的R等價類。
劃分等價類的問題的提法是:要求對S作出符合某些等價性條件的等價類的劃分,已知
集合S及一系列的形如“x等價於y”的具體條件,要求給出S的等價類的劃分,符合所
列等價性的條件。(我們上面提到的聯絡,即可認為是一個等價關係,我們就是要將
集合S劃分成n個聯絡的子集,然後再判斷x,y是否在一個聯絡子集中。)


這三個基本定義對理解並查集的概念及相應的操作非常有幫助。

並查集的操作主要有三個:

1.劃分等價類,這個在預處理的時候進行,很簡單,就是有多少元素就劃分多少等價類即pre[i]=i,很好理解,為下面的合併做好準備。

2.找根節點(溯源) 我通常用函式root(int x)來實現找x的最終祖先。

3.合併,就是將兩個集合合為一個集合。


用演算法實現三個方法:

1.預處理

void init(int n){
	int i;

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


2.溯源

int root(int x){

	if(x!=pre[x]){
		pre[x] = root(pre[x]);
	}
	return pre[x];
}
溯源這裡是有優化的,判斷兩個元素是否屬於同一集合需要O(n)的時間,但這裡可以使用路徑壓縮來進行優化,  路徑壓縮實際上是在找完根結點之後,在遞迴回來的時候順便把路徑上元素的父親指標都指向根結點


3.合併

void merge(int a,int b){

	int x = root(a);
	int y = root(b);
	if(x!=y){
		pre[x]=y;
	}
}

合併就是先找到a和b各自的源,然後把源連起來則兩個集合就實現了合併。


一上這三個函式就是並查集的核心思想和方法,以後的解題關鍵就是能將問題抽象成集合並應用並查集來解決了。



並查集的應用


下面我們就以以上並查集的思想解決一個實際問題來簡單運用一下。

以解決 HDU 1232為例。

題目連結:http://acm.hdu.edu.cn/showproblem.php?pid=1232

題目在這裡就不再贅述了,經過上面的分析,此題就變的很簡單了。

N個頂點連通至少需要N-1條邊,這個是學計算機的都曉得的基礎知識(樹)。此問題總需要的最少邊為total = N-1,每連線兩個不在同一集合中的頂點時,所需要的邊數減一(--total),ok了,最後連完之後的total就是還需要的邊數(也是現在的等價類數目),這個應該也很好理解。


程式實現:

#include <stdio.h>

int total;
int pre[1001];

void init(int n){
	int i;

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

int root(int x){

	if(x!=pre[x]){
		pre[x] = root(pre[x]);
	}
	return pre[x];
}

void merge(int a,int b){

	int x = root(a);
	int y = root(b);
	if(x!=y){
		pre[x]=y;
		--total;
	}
}

int main(){
	int N,M,i,st,end;
	while(scanf("%d",&N) && N){
		
		scanf("%d",&M);

		init(N);

		total = N-1;

		for(i=0;i<M;++i){
			scanf("%d %d",&st,&end);
			merge(st,end);
		}
		printf("%d\n",total);
	}
	return 0;
}

這段程式碼就很好理解了。到此結束了。



相關文章