1. 程式人生 > >並查集:按秩歸併&路徑壓縮

並查集:按秩歸併&路徑壓縮

集合可以怎麼表示?可以用一棵樹來表示,結點表示集合的元素,而樹根則用來代表這個集合。所以用樹來做集合的並查集的話,對於查詢某個元素屬於哪個集合,我們就從這個結點開始往上找,找到它所在的這棵樹的根結點。對於並集操作,只要把兩棵樹的根結點並在一起就可以了。所以為了滿足這樣的操作,我們的樹結構有點小改變,變為雙親表示法“由孩子指向雙親。每個結點都向上指向它的父結點,而不是由父結點向下指向左右子樹。”這樣的樹比較好的是用結構體陣列來儲存表示。

集合建立好後,接下來看查詢操作:

函式把集合和要查詢的元素傳進去,然後第38for迴圈開始查詢這個元素X在集合裡的位置,也就是陣列的下標。i=0開始,i小於MaxSize

也就是陣列的最大下標,並且S[ i ].Data!=X也就是沒找到要查詢的元素的話,就i++。迴圈做完之後,i就等於元素X在結構體陣列中的下標。之和要繼續找到這個元素結點所在樹的根結點,因為根結點的值才是表示一個集合,我們要查詢的是這個元素X屬於哪一個集合。我們知道表示集合的結點的值是負數,且只有它是負數,所以接下來繼續迴圈,如果S[ i ].Parent>=0的話,就表示沒找到表示集合的結點,然後把S[ i ].Parent也就是當前結點的父結點的下標賦值給i,意思是往上一層查詢,直到S[ i ].Parent<0了,代表找到根結點了,就退出迴圈,return i出去。

接著到集合的並運算。並運算是指給出兩個元素的值,讓你把這兩個元素所在的集合併合在一起。這樣操作我們要這樣做:

1、首先要找到這兩個元素所在的樹的根結點(也就是找到這兩個集合)。

2、判斷這兩個元素所在的集合是不是同一個集合,如果不是就做並運算,把其中一個樹較矮的根結點指向另一個樹較高的根結點的陣列下標。


一開始先通過查詢運算找出兩個元素所在樹的根結點,也就是找到各自所在的集合。如果兩個集合不同,就比較哪個集合較高哪個集合較矮。找出較矮的哪個集合就把它的根結點的Parent換成較高樹的根結點的陣列下標,也就是讓較矮的樹指向了較高的樹。

為什麼要做這樣一個查詢操作?如果我們隨便就把兩棵樹合併在一起,會出現怎樣的一種情況?例如如果我們把一棵較高的樹指向一棵較矮的樹,那麼合併後樹的最大高度就會增加,隨著並運算的操作次數越多,樹變得越來越高,越往後那麼集合的查詢操作效率就會變得越來越低,因為查詢操作是從要查詢的結點元素開始一層一層往上找根結點,當集合樹高度很大時,要遍歷的層數就越多,效率就變得越低了。

所以為了解決這個問題,優化樹變高後查詢操作的效率,就要用到按秩歸併。我們應該把較矮的樹指向併到較高的樹上(或者把規模小的樹接到規模大的樹上),這樣較矮的樹高度增加了,但較高的樹高度沒有增加,合併後的整棵樹最大高度仍然是之前較高的樹的高度,所以合併後樹的最大高度並沒有增加。


注意我們的程式碼,因為我們在集合的資料結構中,根結點的Parent是一個負數,且數值是表示這個樹的所有結點的個數(比較高度時,數值為樹的高度),所以比較兩個集合(也就是比較兩棵樹的大小時),根結點的Parent較小的,樹更高。第62行就是說當兩棵樹一樣高時,就隨便把一棵樹併到另一棵樹上,然後合併後的樹高度加一。注意這裡的樹高度加一,在Parent上是做減減運算。

通過按秩歸併對集合的並運算做優化後,程式執行就快了。其實我們可以繼續做優化,用路徑壓縮。

首先我們看回上面的查詢函式


39行,我們每次讀入進來要查詢的元素後,都要讓i等於0開始,也就是從陣列開頭開始線性掃描整個陣列,這樣做的一個不足之處在於,假設陣列有n個元素,我們有n個數要查詢,而假設最壞情況下每次要查詢的元素都是陣列的最後一位也就是第n個,那麼時間複雜度就會是n²。所以我們想,可以不可以把查詢函式裡的這一步線性查詢做點優化?


我們看集合的表示資料結構,裡面的資料域在陣列的每個元素裡都有一個Data來存它。但其實,任何一個有限的集合裡的元素都可以被對映為從0n-1的整數。所以我們想,可以把元素的值直接用陣列的下標來表示。這樣就可以把集合的資料結構裡的ElementType Data去掉了,也就簡化了集合的資料結構,且這樣做的好處在於,在查詢函式裡,我們不用做線性查詢的步驟


我們把要查詢的元素X傳進來後,直接判斷S[ X ].Parent是否小於0,如果是,那就是根結點找著了。否則,就遞迴呼叫Compress函式,把S[ X ].Parent傳進去,也就是找X的父結點,往上一層繼續找根結點。直到S[ X ].Parent<0找到後,就return X

我們用一個例子來解釋下這個函式是怎麼運作的:

例如對這樣一個集合,我們要找的根結點是最上面粉紅色的結點,在我們呼叫查詢函式後,最後我們應該返回就是這個粉紅色的結點。當我們第一次進入查詢函式後,因為最底下的F結點不是我們要找的根結點,所以直接進入查詢函式的第110行的遞迴往上找根結點


直到我們找到根結點後


就把根結點的X的返回出去,返回到上一步的return S[X].Parent=CompressFind(S, S[X].Parent);裡,對於上一個結點來說,在返回之前,會先把根結點返回來的值賦給S[ X ].Parent,也就是把根結點變成父結點。之後再做renturn,而對於這個結點來說,return出去的正是自己的父結點,也就是整個集合的根結點。所以倒數第三個結點也指向了根結點


直到最後把根結點返回給了最初的F結點


這就是路徑壓縮,不僅在查詢操作中找到了集合的根結點,同時把集合的路徑壓縮,這裡的路徑壓縮指把每次掃過的結點都接上根結點上。這樣做的好處在於,在做多次查詢操作時,因為路徑壓縮了,所以會省去很多的時間。

相關推薦

歸併&路徑壓縮

集合可以怎麼表示?可以用一棵樹來表示,結點表示集合的元素,而樹根則用來代表這個集合。所以用樹來做集合的並查集的話,對於查詢某個元素屬於哪個集合,我們就從這個結點開始往上找,找到它所在的這棵樹的根結點。對於並集操作,只要把兩棵樹的根結點並在一起就可以了。所以為了滿足這樣的操作,

合併 $n$ 個點所得樹高不超過 $\lfloor\log n \rfloor$

用 $h_n$ 表示按秩合併 $n$ 個點所得樹的最大高度。 有 $h_1 = 0, h_2 = 1, h_3 = 1, h_4 = 2, h_5 = 2, \dots$ 有如下地推: \[ h_n = \max_{1\le i\le n-1} \max(h_i, h_{n-i}) + [h_i = h_{n

Agri-Net的Kruskal演算法+實現(大小合併+路徑壓縮)

Agri-Net的Kruskal演算法+並查集實現 演算法複雜度分析     對所有的邊進行排序,排序複雜度為O(mlogm),隨後對邊進行合併,合併使用並查集,並查集使用link by size的方式實現,同時在find函式實現了路徑壓縮。每

合併、路徑壓縮

演算法分類: 資料結構 演算法原理: 通過find函式找出該節點的根節點, 通過UNION函式將兩棵樹合併。 加入rank[N]來記錄每個節點的秩(即樹的高度),並按秩進行合併,可避免合併時的最糟糕情況,(樹形為一條直線) 通過路徑壓縮可以減少每次尋找根節點的次數。 演

[2017年第0屆浙江工業大學之江學院程序設計競賽決賽 I] qwb VS 去汙棒(,最小生成樹,LCA)

之間 i++ ont 題意 倍增 題目 while 並查集 工業 題目鏈接:http://115.231.222.240:8081/JudgeOnline/problem.php?cid=1005&pid=8 題意:中文題面。 手動畫一下會發現所求邊必然存在於最大生

可撤銷模板(合併)

大家都很強, 可與之共勉 。 用按秩合併實現,不能路徑壓縮。 class UFS { private : int *fa, *rank ; std :: stack < s

合併)

並查集-按秩合併 題目:UVA-11354 題目大意:給出一張n個點m條邊的無向圖, 每條邊有一個危險度,有q個詢問, 每次給出兩個點s、t,找一條路, 使得路徑上的最大危險度最小。 思路:首先,我們可以發現,如果求一個最小生成樹, 那麼任意兩點, 在生成

HDU 3635 Dragon Balls(路徑壓縮

Problem Description Five hundred years later, the number of dragon balls will increase unexpectedly, so it's too difficult for Monkey King(WuKong) t

POJ 1182 食物鏈 復習

iostream unit color clu string using pan n) else #include <iostream> #include <algorithm> #include <cstring> #incl

擒賊先擒王

name 節點 單元 spa pan 人的 find 子集 還要 定義 並查集,在一些有\(N\)個元素的集合應用問題中,我們通常是在開始時讓每個元素構成一個單元素的集合,然後按一定順序將屬於同一組的元素所在的集合合並,其間要反復查找一個元素在哪個集合中。這一類問題近幾年來

集合合併與元素查詢

博主按:因為教程所示圖片使用的是 github 倉庫圖片,網速過慢的朋友請移步《並查集:集合合併與元素查詢》原文地址。更歡迎來我的小站看更多原創內容:godbmw.com,進行“姿勢”交流 ♪(∇*) 1. 什麼時候需要並查集? 在一些有 N 個元素的集合應用問題中

HDU4496-D-City(倒用

D-City Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65535/65535 K (Java/Others) Total Submission(s): 2448 Accepted

學習總結

剔除 最小值 cst 之間 int 勿噴 決戰 建立 食物 $ ??????????????? ? $學習總結:並查集 蒟蒻的第一篇博客,如有bug,請大佬提出,勿噴。 並查集: 並查集雖說是集合,不過個人覺得類似樹形結構,像森林,剛開始每一個節點是一個森林,不斷把森林合並

樹的父指標表示法(演算法 重量權衡合併規則 路徑壓縮

樹的父指標表示法:對於樹中的每個結點都儲存一個指標域指向父結點。(缺點:不能準確地找到給定的結點的子結點資訊。優點:可以解決判斷倆個結點是否在同一個樹中的問題;用並查演算法合併倆個集合。) 並查演算法:如果倆個結點在同一棵樹,則判斷為同一集合。 實現: 無 0 0 1 1

的兩種優化(合併,路徑壓縮)

並查集是建立在對不相交集合進行的兩種基本操作的基礎之上的。操作之一:檢索某元素屬於哪個集合;操作之二:合併兩個集合。黑書上說了,這種結構顯然可以用連結串列或森林實現,顯然用連結串列進行查詢時間複雜度應該是O(n)級別的,而使用森林進行查訪如果處理的好時間複雜度就應該是O(l

BZOJ 3624 [Apio2008]免費道路 + 生成樹 + 貪心【恰有k條特殊路徑

back fail urn 端點 space namespace bzoj stream def 題目鏈接:http://www.lydsy.com/JudgeOnline/problem.php?id=3624 題意:   給你一個無向圖,n個點,m條邊。   有兩種邊,

算法學習——動態圖連通性(線段樹分治+

mes inline ret bsp getc class 離開 。。 node 在考場上遇到了這個的板子題,,,所以來學習了一下線段樹分治 + 帶撤銷的並查集。 題目大意是這樣的:有m個時刻,每個時刻有一個加邊or撤銷一條邊的操作,保證操作合法,沒有重邊自環,每次操作後

(未搞懂!!!)BZOJ 4668 冷戰(排序+樸素LCA)

Description 1946 年 3 月 5 日,英國前首相溫斯頓·丘吉爾在美國富爾頓發表“鐵 幕演說”,正式拉開了冷戰序幕。 美國和蘇聯同為世界上的“超級大國”,為了爭奪世界霸權,兩國及其 盟國展開了數十年的鬥爭。在這段時期,雖然分歧和衝突嚴重,但雙方都

小白專場 File Transfer--集合的簡化表示,歸併路徑壓縮

集合的簡化表示 原始的集合表示: typedef struct { ElementType Data; int Parent; } SetType; int Find(SetType S[], Elemtype X) { //在陣列S中查詢值為X的元素所屬的集合 //MaxSiz

bzoj3237 [Ahoi2013]連通圖 線段樹分治+合併

Description 給定n個點m條邊的無向圖,k次詢問,每次刪除s條邊並詢問此時圖的連通性,詢問互相獨立。 n<=1e5,m<=2e5,k<=1e5,s<=4 Solution 傳說中的線段樹分治 刪除和插入同時存在的話非常麻煩,因此考