1. 程式人生 > >自己選擇的路,就算跪著也要走完。

自己選擇的路,就算跪著也要走完。

 最近寫了一個多星期的並查集,一瞬間貼出這麼多解題報告,我想關於並查集的應用先告一段落吧,先總結一下。

在網上看到一篇關於並查集比較好的教程(姑且允許我這麼說吧),不轉過來是在可惜。獻給愛學習的你

文章作者:Slyar 文章來源:Slyar Home (www.slyar.com) 轉載請註明,謝謝合作。

等價關係與等價類

從數學上看,等價類是一個物件(或成員)的集合,在此集合中的所有物件應滿足等價關係。若用符號"≡"表示集合上的等價關係,那麼對於該集合中的任意物件x,y, z,下列性質成立:

1、自反性:x ≡ x

2、對稱性:若 x ≡ y 則 y ≡ x

3、傳遞性:若 x ≡ y 且 y ≡ z 則 x ≡ z

因此,等價關係是集合上的一個自反、對稱、傳遞的關係。

等價關係

通過金屬線連線起來的電器的連通性,就是一種等價關係。這種關係顯然具有自反性,因為任何一個器件都是與自身連通的;如果a 電連通b,那麼b一定也電連通a,因此這種關係具有對稱性; 若a連通到b,並且b連通到c,那麼a連通到c 。

並查集

並查集的一般用途就是用來維護某種具有自反、對稱、傳遞性質的關係的等價類。並查集一般以樹形結構儲存,多棵樹構成一個森林,每棵樹構成一個集合,樹中的每個節點就是該集合的元素,找一個代表元素作為該樹(集合)的祖先。

並查集支援以下三種操作:

1、Make_Set(x) 把每一個元素初始化為一個集合

初始化後每一個元素的父親節點是它本身,每一個元素的祖先節點也是它本身。

2、Find_Set(x) 查詢一個元素所在的集合

查詢一個元素所在的集合,只要找到這個元素所在集合的祖先即可。判斷兩個元素是否屬於同一集合,只要看他們所在集合的祖先是否相同即可。

3、Union(x,y) 合併x,y所在的兩個集合

合併兩個不相交集合操作很簡單:首先設定一個數組Father[x],表示x的"父親"的編號。那麼,合併兩個不相交集合的方法就是,找到其中一個集合的祖先,將另外一個集合的祖先指向它。

合併

並查集的優化

1、Find_Set(x)時 路徑壓縮

尋找祖先時我們一般採用遞迴查詢,但是當元素很多亦或是整棵樹變為一條鏈時,每次Find_Set(x)都是O(n)的複雜度,有沒有辦法減小這個複雜度呢?

答案是肯定的,這就是路徑壓縮,即當我們經過"遞推"找到祖先節點後,"迴歸"的時候順便將它的子孫節點都直接指向祖先,這樣以後再次Find_Set(x)時複雜度就變成O(1)了。

路徑壓縮

2、Union(x,y)時 按秩合併

即合併的時候將元素少的集合合併到元素多的集合中,這樣合併之後樹的高度會相對較小。

主要程式碼實現

+++++++++++++++++++++++++++++++++++++++

/* father[x]表示x的父節點 */
int father[MAX];
/* rank[x]表示x的秩 */
int rank[MAX];

/* 初始化集合 */
void Make_Set(int x)
{
	father[x] = x;
	rank[x] = 0;
}

/* 查詢x元素所在的集合,回溯時壓縮路徑 */
int Find_Set(int x)
{
	if (x != father[x])
	{
		father[x] = Find_Set(father[x]);
	}
	return father[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;
	}
	else
	{
		if (rank[x] == rank[y])
		{
			rank[y]++;
		}
		father[x] = y;
	}
}

+++++++++++++++++++++++++++++++++++++++

相關題目

並查集的基礎應用:

最小生成樹Kruskal演算法並查集應用: