資料結構 9 並查集(不相交集)

2022-03-30 19:13:45 字數 3724 閱讀 6165

並查集(disjoint sets),直譯即不相交集。

離散數學中對等價關係的定義:滿足自反性、對稱性和傳遞性的關係。

集合a,∀(a,b),a,b∈a,滿足arb,則稱r為a上的關係,若r滿足以上三種性質,則為等價關係。

數學上的定義不必過多解釋,只需知道,等價關係是用來對集合中的元素分類,以達到簡化問題的目的的。

舉個例子,a,b,c,d,e,f,g∈a,若對於a上的等價關係r,有arb,arc,ard,erf,frg,則集合a被劃分為兩個等價類,即,}。

套用到實際應用中,a,b,c,d具有等價關係,則a,b,c,d可以被認為是同類事物,同理,e,f,g可以被認為是另一類事物。

並查集主要實現兩個功能:並(union)和查(find)

等價關係一旦確立往往不會改變,但據此劃分的等價類並不總是一成不變。

例如,朋友圈這種關係是長久存在的,a和b是朋友,c和d是朋友,於是形成了兩個不同的圈子,即a和b,c和d分別同在乙個等價類中。

某一天,a和c相遇了,他們覺得在一起ac很好玩(霧),於是進入了彼此的朋友圈,根據等價關係的傳遞性(朋友的朋友是朋友),b -a - c- d,這兩個圈子有了交集,a,b,c,d形成了乙個圈子,這就發生了等價類的合併(union)。

儘管從朋友圈的定義來講,b和d因為a和c的關係已經進入了同乙個朋友圈,但是他們兩個實際上還並未互相認識。

某一天,b和d也偶遇了,他們覺得bd是絕配(大霧),也想進入彼此的朋友圈,但是他們一看朋友圈(find),發現原來他們已經是同乙個圈子裡的人了,兩人相視一笑。

對於find操作,最快的無疑是雜湊(用陣列儲存等價類的名字),可以達到o(1)。

但是,如果採用這種資料結構,當執行union時,將不得不掃瞄一遍陣列,此時複雜度達到了o(n)。看上去還不錯?然而實際應用中,union往往是頻繁的,當它的優勢:find的次數不夠多時,o(n)的複雜度是無法被接受的。

對於find操作,並不要求其返回特定的名字,而只要求當且僅當兩個元素屬於同乙個集合時,他們能夠返回同樣的名字。

以朋友圈為例,沒有人(大概)會在事先為自己所在的朋友圈專門命名,b和d要確認他們是否已經在同乙個朋友圈,只需要說出他們朋友圈裡乙個共同的朋友(返回相同的名字)就可以了,至於這個共同的朋友是a還是c並不重要。

於是使用樹來表示集合,樹上的每乙個元素都有相同的根,用根來命名所在的集合,這些樹組成森林,即不相交集合森林

這些樹不一定必須是二叉樹,其節點中唯一需要儲存的資訊是其父指標(秩充當了元素的名字)。

不相交集合森林儲存在陣列p中,p[i]的值表示i的父親,若i為根,則p[i] = 0,所以,陣列的所有的值被初始化為0。

union:為使兩個集合(設為集合i和集合j)合併,只需將其中乙個節點中儲存的父指標指向另外乙個節點。由誰指向誰有多種策略,但毫無疑問,此時union僅需花費常數時間o(1)。

套用到朋友圈的例子中就是,兩個人為了確認是否已經在同乙個朋友圈,通過朋友圈的傳遞性,在整個朋友圈繞了一大圈才發現彼此是朋友。

如上圖,從4,9出發,不得不一直向上,直到找到1為止。

要避免最壞的情況出現,一則需要在union時選取合適的策略,二則需要這棵樹能做自優化,自動減小深度,即路徑壓縮演算法。

const

int maxn = 1000

;int

pre[maxn];

void init(int

n)int find(int

x)//

y->x,並非最優

void setunion(int x, int

y)

樹越深,則並查集的效率越差。

直接求並往往會導致樹越來越深,類似二叉搜尋樹的不平衡。

有兩種求並方法可以避免

1.按大小求並:用乙個陣列記錄樹的大小,合併時,總是讓小的樹並到大的樹上。

2.按高度求並(按秩求並):記錄樹的高度,合併時,總是讓矮的樹並到高的樹上。

記錄高度是很麻煩的,所以只記錄高度的估計,即用秩代替。按高度求並是事實上的按秩求並。

下圖可以直觀地感受到

直接求並和按大小求並的區別

按大小求並比直接求並少了一層。

記錄樹的大小並不難,似乎新開闢乙個陣列就可以辦到。而事實上,甚至不需要額外的空間,基於儲存樹的陣列就可以直接做到。

上面初步實現的並查集中,把所有陣列元素初始化為0,代表i是根,除此之外0似乎沒有什麼意義。

事實上,在有的實現版本中,初始化時將它們初始化為對應的秩,即p[i]  = i,以此代表i是根。

在經歷合併之後,那些非根的陣列元素變成父指標,但根對應的陣列的值始終沒有變化(總是為0)。

不妨讓這部分值承擔更多的責任,記錄樹的大小。

問題在於,記錄樹的大小會導致無法識別出根(該怎樣區分陣列中記錄的是父指標還是樹的大小呢?)。

不難發現,非根的結點對應陣列的值,即父指標,所指向的是陣列的秩,而秩總是非負的。所以,大可直接在根的陣列元素中儲存樹的大小的負值,這需要在初始化時將所有陣列元素設定為-1

按大小union實現如下

void init(int

n)void setunion(int x, int

y)

else

}

按大小求並的平均效率可達到o(1),因為執行過程中大部分合併都是很小的集合合併到大集合中

在根陣列元素中記錄深度的負值,只需在按大小求並的基礎上稍作修改即可。

void init(int

n)void setunion(int x, int

y)}

在按高度求並演算法中,只有當兩棵樹的高度相同時,樹的高度才會增加1。

大多數情況下,上面實現的並查集完全可以被接受。但最壞情況仍然很容易發生。

《資料結構與演算法分析——c語言描述》中舉了這樣乙個例子

把所有的集合放到乙個佇列中,並重複地讓前兩個集合出隊,同時讓它們的併入隊,最壞的情況就會發生。

如下圖

此時無論選取哪種union策略都是無法避免的,這就需要路徑壓縮。

路徑壓縮的思路非常簡單,在進行一次find(x)後,可以確定,x屬於某個集合,與其下次find時再次歷盡千辛萬苦從x找根,不如直接將x設定為根的子節點。其實現也很簡單,只需在原有的基礎上做一點微小的改動。

int find(int

x)

這一過程自動發生在find中,與union無關,所以與上述兩種union操作並不衝突。

hduoj 1232 暢通工程

leetcode 547 朋友圈

題解

leetcode 1466 重新規劃路線

題解:挖坑待填

超有愛的並查集~

不相交集合的資料結構 並查集

在介紹操作之前,我得先說說實現這些操作的背景。對於並查集中的每乙個集合,都有乙個代表,這個代表就是集合中的乙個元素,其表示了整個集合。打個比喻吧,最近召開了19大,各個代表都召集到了人民大會堂,假設每個代表都代表著某個省份的人去參加會議,比如我是江西的,江西省的人大代表就代表了江西人民參加會議進行投...

並查集(不相交集合)

早上早早起來看kruscal的mst演算法,原來要用到不相交集合來實現。拿起 演算法導論 看完不相交集合這章,頓然茅塞頓開,終於完成並查集的基礎知識的學習。演算法導論 真是牛 不相交集合有兩種不同的實現,鍊錶表示和帶路徑壓縮的按秩合併策略。看到大家都比較喜歡用帶路徑壓縮的按秩合併策略,那麼我只認真研...

並查集(不相交集)ADT

等價關係 需要同時滿足下列三個性質的關係r 等價集合 如果乙個元素a 屬於集合s,則元素a的等價集合是集合s的乙個子集,它包含所有與元素a有等價關係的元素。輸入資料最初是n個元素 元素也是乙個集合 的集合,其中每個集合只含有乙個元素,且互不相同,也不存在等價關係,使得這些集合互不相交,此時只能進行兩...