在我們平常生活中,無論是人與人之間還是動物與動物之間,都有直接或間接的關係,比如親戚關係等,那麼如果我給你一堆人的親戚關係,並且問你這一堆人中的某兩個人是否是親戚關係,這個該如何實現呢?
我們可以假設,沒有親戚關係的兩個人屬於不同的家族,而家族我們又可以假設為集合,即這兩個人屬於不同的集合。
這顆樹表示同一家族的人,不同的樹表示不同的家族,所以我們可以用樹來表示家族(即集合)。
這裡就到了引入並查集的時候了。
並查集的步驟是怎樣的呢?
首先:我們先假設每個人剛開始都是乙個集合(即自己是乙個家族)。
然後:每次給出具有親戚關係的兩人時,就把這兩個家族合併(即把這兩個集合合併),如果這兩個人本來就屬於同一家族則不需要合併。
最後:每次詢問兩個人是否具有親戚關係,我們只需要看他們是否處在同一集合即可。
實現的**和注釋如下(最初代的):
//並查集的實現需要用到乙個陣列,我們定義乙個全域性陣列來實現集合的功能。int fa[10005];(1) 初始化並查集void init()//初始化並查集(2) 合併結合void merge(int x,int y)//並查集合並結合(3) 查詢節點所在集合int find(int x)//查詢節點所在集合
這時乙個初代的並查集就完成了。
但是我們發現乙個問題,這個初代的並查集效率特別低。
它的效率和樹的深度有關,樹的深度越大,查詢所需要的時間就越長,所以如果要改善該演算法,我們可以減小樹的深度,那麼我們該怎麼改進呢?
1. 路徑壓縮
給出下面兩幅圖來說明:
很明顯,圖一的樹的深度為5,圖二的樹的深度為2,並查集詢問操作時,圖一最壞的情況要找五次才能找到所在集合,圖二的最壞的情況只需要找兩次即可找到所在集合,當資料量大的時候,在時間的效率上,明顯圖二對應的情況效率更高。
那麼我們如何達到圖二這樣的效果呢?
就如圖中的元素a,b,c,d,e,f,我們知道它們屬於同一集合,並不需要知道他們的上乙個元素是誰,而是只需要知道自己所在的集合,所以我們讓所有的節點都指向根節點(即b,c,d,e,f指向a)。
那麼該操作如何實現呢?我們無論是在詢問操作還是在合併操作中都需要知道元素所在的集合,所以我們只需要在查詢元素所在的集合的函式稍作修改即可,即更新路徑(路徑壓縮)。
舉個例子,過程如下:
假設有一集合有a,b,c,3個元素,另乙個集合有d,e,兩個元素,現在需要將兩個集合合併。
合併後變成這樣:
是不是和我們最優的圖不太一樣。
假設下一次呼叫該集合時,路過元素e,我們即可把元素節點e指向它的根節點。
因為無論詢問還是合併操作,都需要知道自己所在的集合,所以我們只需要對查詢自己所在的集合的函式稍作修改即可。
修改後的find函式如下:
int find(int x)//查詢節點所在集合
2. 按秩合併
什麼是按秩合併呢?假如你現在要合併一棵高度為3的樹和高度為2的樹,你是要讓高度為3的樹併入高度為2的樹還是高度為2的樹併入高度為3的樹呢?
下面給出兩種併入結果
高度為3的樹併入高度為2的樹:
高度為2的樹併入高度為3的樹:
很明顯,兩種合併的方法,合併後所得的樹的深度不同,我們前面知道了並查集的效率和樹的深度有關,所以明顯將高度為2的樹併入高度為3的樹後面的效率更好,即將深度小的樹併入深度大的樹。
那麼我們如何實現這個過程呢?
假設我們定義乙個陣列
int rank[10005];
用來表示每個元素對應集合的樹的深度,初始時每個元素自己就是乙個集合,所以rank的初始值為1。
剩下的我們只需要修改合併函式即可,修改後的合併函式為:
void merge(int i,int j)//按秩合併
模板例題(題目取自洛谷):
題目背景:
若某個家族人員過於龐大,要判斷兩個是否是親戚,確實還很不容易,現在給出某個親戚關係圖,求任意給出的兩個人是否具有親戚關係。
題目描述
規定:x和y是親戚,y和z是親戚,那麼x和z也是親戚。如果x,y是親戚,那麼x的親戚都是y的親戚,y的親戚也都是x的親戚。
輸入格式
第一行:三個整數n,m,p,(n<=5000,m<=5000,p<=5000),分別表示有n個人,m個親戚關係,詢問p對親戚關係。
以下m行:每行兩個數mi,mj,1<=mi,mj<=n,表示mi和mj具有親戚關係。
接下來p行:每行兩個數pi,pj,詢問pi和pj是否具有親戚關係。
輸出格式
p行,每行乙個』yes』或』no』。表示第i個詢問的答案為「具有」或「不具有」親戚關係。
輸入輸出樣例
輸入6 5 3
1 21 5
3 45 2
1 31 4
2 35 6
輸出yes
yesno
題目解答:
題目先給出我們每個人的親戚關係,然後要求我們判斷兩個人是否是親戚。這是很明顯的並查集題目,有親戚關係的人同在乙個集合,判斷時只需要判斷兩個人是否在同一集合就可以了。
題解**:
#include#include#include#include#include#include#include#includeusing namespace std;#define ll long longint fa[5005];int find(int x)//找到元素所在的集合int merge(int x,int y)//並查集合並void init()//初始化並查集int main() for(int i=0;i
//p個詢問
}
並查集路徑壓縮 並查集 UnionFind 入門
咳咳,剛看完海賊更新,馬上呼哧呼哧寫下這篇文章,這週的目標就是出一篇並查集相關的文章,真的是時間咻咻一下就沒了。本文閱讀大概需要3分鐘。好了,並查集呢,英文叫union find 並查集是一種樹型的資料結構,通常來用於處理一些不相交集合的合併 union 問題,以及查詢 find 問題。如下 1 有...
並查集 壓縮路徑
並查集 union findsets 一種簡單的用途廣泛的集合.並查集是若干個不相交集合,能夠實現較快的合併和判斷元素所在集合的操作,應用很多,如其求無向圖的連通分量個數等。最完美的應用當屬 實現kruskar演算法求最小生成樹。並查集的精髓 即它的三種操作,結合實現 模板進行理解 1 make s...
並查集路徑壓縮
使用並查集查詢時,如果查詢次數很多,那麼使用樸素版的查詢方式肯定要超時。比如,有一百萬個元素,每次都從第一百萬個開始找,這樣一次運算就是10 6,如果程式要求查詢個一千萬次,這樣下來就是10 13,肯定要出問題的。這是樸素查詢的 適合資料量不大的情況 int findx int x 下面是採用路徑壓...