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;
} 



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