树形结构 —— 并查集 —— 基本操作
【初始化】
并查集在最開始時,所有的元素各自單獨(dú)構(gòu)成一個集合。
當(dāng)集合中只有一元素時,這個集合的代表結(jié)點(diǎn)即為該元素,即該元素的父親(father)是其自身。
因此并查集的初始化即將每個元素以自己作為自己的根結(jié)點(diǎn)。
int n; int father[N]; void init(){for(int i=1;i<=n;i++)father[i]=i; }【查詢根結(jié)點(diǎn)編號】
查詢根結(jié)點(diǎn)編號是并查集中最基礎(chǔ)的用法,即我們給出一個元素編號 x 時,能通過 Find(x) 操作尋找到元素 x 的父結(jié)點(diǎn)編號。
1.基本實現(xiàn)
我們知道,一個根結(jié)點(diǎn)的父結(jié)點(diǎn)編號是其自身,即:father[root]=root,借助這個性質(zhì),我們即可找到任意一個結(jié)點(diǎn) x 的根結(jié)點(diǎn)。
如下圖,我們知道 father[1]=1,father[2]=1,father[3]=1,father[4]=2,father[5]=2,father[6]=3,假設(shè)我們要找 6 號結(jié)點(diǎn)的根結(jié)點(diǎn),那么過程為:首先找到 6 號點(diǎn)的父結(jié)點(diǎn)為 3 號,然后再找 3 號的父結(jié)點(diǎn)為 1 號,再找 1 號的父結(jié)點(diǎn)為 1 號,此時發(fā)現(xiàn) 1 號點(diǎn)父結(jié)點(diǎn)是其自身,即滿足 father[root]=root,說明找到了 6 號點(diǎn)的根結(jié)點(diǎn),即 1 號點(diǎn)
因此,我們利用 while 循環(huán),即可非遞歸地實現(xiàn) Find(x) 操作
void Find(int x){//非遞歸實現(xiàn)while(father[x]!=x)//未查詢到根結(jié)點(diǎn)時x=father[x];//將當(dāng)前結(jié)點(diǎn)更新為其根結(jié)點(diǎn),繼續(xù)向上尋找return x; }由于并查集中每個集合都是一棵樹,那么進(jìn)一步,我們借助樹可以利用遞歸來完成上述操作
void Find(int x){//遞歸實現(xiàn)if(father[x]!=x)//未查詢到根結(jié)點(diǎn)時return Find(father[x]);//以當(dāng)前結(jié)點(diǎn)的父結(jié)點(diǎn)進(jìn)一步查詢return father[x]; }2.路徑壓縮
可以發(fā)現(xiàn),我們尋找 x 的父結(jié)點(diǎn)的過程中,不停的通過 father[x] 數(shù)組向上去尋找它的父結(jié)點(diǎn),在這個過程中,相當(dāng)于把這個結(jié)點(diǎn)到其根結(jié)點(diǎn)的這條路徑上的所有結(jié)點(diǎn)都遍歷了一遍,這無疑加大了時間復(fù)雜度。
為解決上述問題,就有了并查集結(jié)構(gòu)中最重要的優(yōu)化——路徑壓縮,經(jīng)過路徑壓縮后,使得再次查詢這條路徑上結(jié)點(diǎn)的根結(jié)點(diǎn)時,只要 O(1) 的時間復(fù)雜度即可得到。
路徑壓縮的本質(zhì)是減少樹的層數(shù),對于一棵集合樹來說,其根結(jié)點(diǎn)下依附著諸多結(jié)點(diǎn),在 Find(x) 的過程中,從底向下,我們遞歸的對結(jié)點(diǎn)進(jìn)行更新,如果某個結(jié)點(diǎn)不是根結(jié)點(diǎn)的話,我們就將這個結(jié)點(diǎn)的父結(jié)點(diǎn)設(shè)為其父結(jié)點(diǎn)的父結(jié)點(diǎn),逐層的進(jìn)行遞歸,盡最大可能的去減少樹的層數(shù)。
int Find(int x) {if (x != father[x])return father[x] = Find(father[x]);return father[x]; }舉例來說,假設(shè)初始并查集如下:father[1]=1,father[2]=1,father[3]=2,father[4]=3,father[5]=4,現(xiàn)在我們要進(jìn)行 Find(4) 操作
根據(jù)上述遞歸的路徑壓縮的過程:
可以看出,路徑壓縮完成后,有:father[1]=1,father[2]=1,father[3]=1,father[4]=1,father[5]=4,從 4 號點(diǎn)到 1 號點(diǎn)的這條路徑被壓縮了,當(dāng)下一次查詢,只需要經(jīng)過一次詢問,即可得到相應(yīng)結(jié)點(diǎn)的根結(jié)點(diǎn)。
【合并兩集合】
合并兩集合同樣是并查集中常見的操作。
對于分屬兩個集合中的元素 x、y,首先利用 Find() 操作,找出兩個結(jié)點(diǎn)的根結(jié)點(diǎn) fx、fy,然后判斷根結(jié)點(diǎn)是否相同,若相同,說明兩結(jié)點(diǎn)已經(jīng)處于一個集合里,不需合并,若不同,將元素 y 的根結(jié)點(diǎn) fy 指向 x 的根結(jié)點(diǎn) fx 即可
void Union(int x,int y) {int fx=Find(x);//x的根結(jié)點(diǎn)int fy=Find(y);//y的根結(jié)點(diǎn)if(fx!=fy)//判斷fx與fy是否為一個根結(jié)點(diǎn)father[fy]=fx; }【判斷兩元素是否屬于同一集合】
判斷兩元素是否屬于同一集合,只需要利用 Find() 操作找出兩個元素的根結(jié)點(diǎn) fx、fy,判斷是否相等即可。
bool judge(int x,int y){int fx=Find(x);int fy=Find(y);if(fx==fy)return true;return false; }【統(tǒng)計集合數(shù)目】
在初始化時,我們將每個元素以自己作為自己的根結(jié)點(diǎn),因此當(dāng)需要統(tǒng)計集合數(shù)目時,只需要統(tǒng)計有多少個元素的根結(jié)點(diǎn)是其自身即可。
int countSets(int n){int cnt=0;for(int i=1;i<=n;i++)if(father[i]==i)cnt++;return cnt; }【統(tǒng)計每個集合中元素個數(shù)】
統(tǒng)計每個集合中元素的個數(shù)需要兩步:首先,對于 n 個結(jié)點(diǎn),我們要先尋找他們的父結(jié)點(diǎn),借助桶排來統(tǒng)計父結(jié)點(diǎn)下的結(jié)點(diǎn)個數(shù),然后,再統(tǒng)計父結(jié)點(diǎn)外的點(diǎn),即對于每個元素,將其個數(shù)記為其父結(jié)點(diǎn)下的元素個數(shù)。
void countElements(){for(int i=1;i<=n;i++){father[i]=Find(i);//尋找每個節(jié)點(diǎn)的父結(jié)點(diǎn)num[father[i]]++;//統(tǒng)計父結(jié)點(diǎn)下的節(jié)點(diǎn)個數(shù)}for(int i=1;i<=n;i++)//統(tǒng)計父節(jié)點(diǎn)外的點(diǎn)的個數(shù)num[i]=num[father[i]];//對于每個元素,將其個數(shù)記為其父結(jié)點(diǎn)下的元素個數(shù) }總結(jié)
以上是生活随笔為你收集整理的树形结构 —— 并查集 —— 基本操作的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 过河卒(信息学奥赛一本通-T1314)
- 下一篇: 动态规划 —— 背包问题 P04 ——