重談並查集

2022-03-20 03:38:15 字數 1098 閱讀 2721

當初學並查集的時候記得是森哥講的@zcs0724。但是沒太學明白(是因為蒟蒻太菜,跟森哥沒半毛錢關係),於是今天來重新捋一下並查集,順便補個講解。就叫重談並查集吧。(沒有爆粗)(滕總笑)

並查集(disjoint-set)是一種可以維護若干個不重疊的集合,並支援集合合併與查詢的資料結構。

(來自李煜東《演算法競賽高階指南》)

簡單來講,就是乙個支援兩個集合合併,支援查詢乙個元素在哪個集合裡的資料結構。也就是有合併和查詢兩種操作。

剛剛提到的並查集的基本概念是建立在「集合」這個定義上的。那麼,我們肯定要選擇一種方法來把集合表示出來。可以看出的是,如果對集合從1開始進行編號,那麼最終的效率應該是很低的,所以我們選擇用乙個代表元素來表示這個元素所在的整個集合。

那麼,如果選擇維護乙個陣列\(f[i]\)表示\(i\)元素所在集合的編號(也就是代表元素),在每次合併的時候需要大量修改\(f[i]\),效率非常低。於是,我們繼續思考,使用常見的資料結構:樹來儲存集合,令代表元素為樹根。那麼,乙個並查集就是乙個森林,一棵樹就是乙個集合。我們只需要維護\(fa[i]\)表示這個節點的父親,特別的,令樹根的父親為他自己。那麼,在我們查詢的時候,就可以一直遞迴查到根節點,即為答案。合併的時候,直接令一棵樹的樹根為另一棵樹的樹根的子節點即可。

但是遞迴就很慢。

怎麼辦呢?

剛剛提到過,我們的並查集已經優化到通過維護樹來維護集合。但是在查詢的時候,這樣的最壞複雜度是\(o(n)\)級別的。所以我們要考慮如何對其進行優化。

思考優化其實就是在思考如何把最壞狀況變成最好狀況。很容易看出,這棵樹的最好狀況是只有兩層,也就是所有節點要麼是根節點,要麼是它的兒子。

靈光一閃:那麼,如果我們在每次查詢的時候,都把路徑上經過的所有節點都直接指向根節點呢?是不是在之後的查詢中就快很多了?複雜度是均攤\(o(\log n)\)的。

關於啟發式合併,是多種資料結構合併時的一種思想,具體的蒟蒻有專門隨筆講解:

**啟發式合併

在這裡給出帶注釋的路徑壓縮並查集的**實現:

int find(int x)

int find(int x)

void merge(int x,int y)//合併x,y元素所在的集合

並查集 並查集

本文參考了 挑戰程式設計競賽 和jennica的github題解 陣列版 int parent max n int rank max n void init int n int find int x else void union int x,int y else 結構體版 struct node ...

並查集入門(普通並查集 帶刪除並查集 關係並查集)

什麼是並查集?通俗易懂的並查集詳解 普通並查集 基礎並查集 例題 題解 how many tables problem description lh boy無聊的時候很喜歡數螞蟻,而且,還給每乙隻小螞蟻編號,通過他長期的觀察和記錄,發現編號為i的螞蟻會和編號為j的螞蟻在一起。現在問題來了,他現在只有...

並查集,帶權並查集

題意 ignatius過生日,客人來到,他想知道他需要準備多少張桌子。然而一張桌子上面只能坐上相互熟悉的人,其中熟悉可定義成為a與b認識,b與c認識,我們就說a,b,c相互熟悉 例如a與b熟悉and b與c熟悉,d與e熟悉,此時至少需要兩張桌子。輸入 t表示樣例個數,n表示朋友個數,朋友從1到n編號...