超級簡單並查集詳解

一、概述

並查集,在一些有N個元素的集合應用問題中,我們通常是在開始時讓每個元素構成一個單元素的集合,然後按一定順序將屬於同一組的元素所在的集合合併,其間要反覆查找一個元素在哪個集合中。

其實說白了大部分還是用於尋找兩個點是否聯通,求最小生成樹。

二、分析

1、背景闡明

首先我們需要先闡明一個簡單的道理,假如從A城能到B城,從B城能到C城,那麼自然A城和C城聯通。並查集便是基於這個理論。

舉個例子,在某黑廠中,員工們吃飯時往往三兩成羣(每個小羣體都有一個比較有主見的人決定中午吃什麼,當然,兩個很主見的人不可能在一個小集體,但是有的可能僅僅是爲了和朋友在一起,並不關心到底吃什麼),比如志熊喜歡拉上員工小C一起出去吃,gmk喜歡和whr、rzw三人在食堂解決,而xuao恩自己帶飯。於是大概就變成了這樣一張圖:

(頭上原諒星的是這個團隊裏的主心骨) 

2、查找

假如說員工小C的外校朋友小D想和小C一起吃個飯追溯一下革命友情(手動滑稽),他會找小C這個集團的主心骨協商會面,很可惜,小C不是這個集團的主心骨,那如何找到呢?

閒話不多說,直接上代碼來理解。

我們定義一個find函數和一個數組pre[ ](用來記錄他“想跟隨”的朋友)。

這個代碼又分爲遞歸寫法和非遞歸寫法(其實都一樣)。

遞歸版:

int find(int x)//遞歸版本通俗易懂 
{
	if(pre[x]!=x)//如果它的前驅節點不是他自己
	//(如果他不是他們團隊的主心骨) 
	  return find(pre[x]);//詢問他的前驅節點
	//(找他跟隨的朋友) 
	else//如果它的前驅節點是他自己
	//(他就是這個團隊的主心骨) 
	  return x;//返回 
}

非遞歸版:

​int find(int x)//非遞歸,在網上更常見一些 
{
	while(pre[x]!=x)
	  x=pre[x];
	return x;
}

3、合併 

有一天,員工小C跟隨的好友志熊覺得 跟學長lyb和dhy混也不錯,於是他們決定加入他們。

於是圖變成了這樣:

志熊決定跟隨學長dhy,所以吃什麼已經不由他來決定了(頭上的原諒星也隨之消失),如圖志熊選擇跟隨dhy,小C認爲還是和朋友在一起好,也會跟隨志熊,lyb如常跟隨dhy,這樣新的四人團體就構成了。 

然而代碼如何實現呢,這非常簡單:

void join(int x,int y)
{
    father[find(x)]=find(y);
}

4、路徑壓縮 

員工小C有一天突然覺得 和學長密切交流的機會 不能全給志熊啊,他決定直接跟隨學長dhy,不再跟隨志熊了。

如下圖所示:

而這一步代碼如何實現呢:

int zip(int x)//路徑壓縮
{
	if(pre[x]!=x)//如果它的前驅節點不是他自己
	//(他不是這個小團體的主心骨) 
	pre[x]=find(pre[x]);//他直接跟隨老大 
	return x;
}​

 我們可以看到經過路徑壓縮的節點及其子樹到根節點的深度減小了很多,所以在以後的操作中,查找根節點的速度會快很多,這就是路徑壓縮的意義。

5、按秩排序

zsy看到了兩個集團的合併,決定也加入他們,此時zsy有一個夥伴,而dhy有三個。人多力量大麼,自然人少了要向人多的靠攏,如圖所示:

上代碼:

void unionn(int r1,int r2)//秩排序 rank用來記錄隨從的多少 
{
	int fr1=find(r1);//找到r1團隊的主心骨 
	int fr2=find(r2);//找到r2團隊的主心骨 
	if(fr1==fr2)//自己人,走吧 
	  return;
	if(rank[fr1]>=rank[fr2])//r1的人多 
	{
		f[fr2]=fr1;//r2成爲r1的一部分 
		rank[fr1]+=rank[fr2];//r1多了很多"隨從" 
		
	}
	else//r2的人多
	{
		f[fr1]=fr2;//r1成爲r2的一部分 
		rank[fr2]+=rank[fr1];//r2多了很多"隨從" 
	}
}

當然每次別忘了 把rank數組初始化(在輸入的同時)

對於並查集來說,這是一種優化,將小樹移到大樹上,可以有效降低整個樹的深度。

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