專題:並查集
一、引入
在一些有n個元素的集合應用問題中,我們通常是在開始時讓每個元素構成乙個單元素的集合,然後按一定順序將屬於同一組的元素所在的集合合併,其間要反覆查詢乙個元素在哪哪個集合中。
該問題看似並不複雜,但資料量極大,若用正常的資料結構來描述的話,往往超過了空間的限制,計算機無法承受;而且複雜度較高,實現過程較複雜。因此,只能採用一種特殊資料結構——並查集來描述。
二、定義
並查集是一種用於分離集合操作(管理元素分組情況)的抽象資料型別。它所處理的是「集合」之間的關係,即動態地維護和處理集合元素之間複雜的關係。
當給出兩個元素的乙個無序對(a,b)時,需要快速「合併」a和b分別所在的集合,這其間需要反覆「查詢」某元素所在的集合。「並」、「查」和「集」三字由此而來。在這種資料型別中,n個不同的元素被分為若干組。每組是乙個集合,這種集合叫做分離集合。
三、結構
使用樹形結構實現,不過不是二叉樹。
集合中的每個元素對應乙個節點,每個集合對應一棵樹。在並查集中,哪個節點是哪個節點的父親以及樹的形狀等資訊無需多加關注,整體組成乙個樹形結構才是重要的。
四、基本操作
並查集支援查詢乙個元素所屬的集合 以及 兩個元素各自所屬的集合的合併 兩種操作。
1. 初始化:
使用n個節點表示n個元素,最開始時沒有邊。
2. 合併:
相當於將兩個集合合併為乙個集合(即求並集的過程),假定在此操作前兩個集合是分離的。
3. 查詢:
查詢兩個節點是否屬於同一集合(即他們是否有相同的根節點),我們需要沿著樹向上走,來查詢包含這個元素的樹的根是誰。如果兩個節點走到了同乙個根,則可說明它們屬於同一集合。
舉例:
五、優化
1. 當樹形結構發生「退化」,即趨於線性結構時,對並查集的基本操作的複雜度就會非常高。因此,有必要優化儲存結構,想辦法避免「退化」的發生。
2. 考慮高度合併的方法:對每棵樹,記錄樹的高度rank;合併時,如果兩棵樹的rank不同,那麼從rank小的向rank大的連邊。
3. 並查集的路徑壓縮(乙個重要且典型的方法)
(1)路徑壓縮實際上是在找完根結點之後,在遞迴回來的時候順便把路徑上元素的父親指標都指向根結點。
(2)以上圖為例,我們在「合併5和3」的時候,不是簡單地將5的父親指向3,而是直接指向根節點1,如圖:
(3)在使用這種簡化的方法時,為簡便起見,即使樹的高度發生了變化,也不修改rank的值。
4. 複雜度分析:o(ɑ(n)),是乙個」均攤複雜度」,比o(log(n))還要快。
六、**實現(以陣列實現為例)
#include #define maxn 100005
int n,m,q; //建立含n個元素的集合(編號1~n),進行m次合併、q次查詢
int r1,r2,x,y; //將x和y節點所屬集合合併
int parent[maxn]; //parent[i]表示元素i的父親節點
int rank[maxn]; //樹的高度
void init(int n) //初始化並查集}/*
int find(int x) //查詢樹的根(非遞迴實現)
*/int find(int x) //查詢樹的根(遞迴實現)
void union(int x,int y) //合併節點x和y所屬的集合
p[maxn];
void init(int n)
}int find(int n)
void union(int x,int y)
stu[maxn];
void init(int n)
}int findroot(int n)
void union(int x,int y)
f[maxn][maxn],k1,k2;
void init(node f[maxn])//初始化點陣(並查集)
}}node findroot(node k) //找根節點
int main()
a[3*maxn];
void init(int n)
}int findpre(int n)
void union(int x,int y)
if(d==1) //"x和y屬於同一類"的資訊
}else //"x吃y"的資訊}}
printf("%d\n",ans);
return 0;
}
***********************************====拓展部分***********************************===
1. 求無向圖的連通分量
求無向圖連通分量是個非常常用的演算法。通過並查集可以使得空間上省去對邊的儲存,同時時間效率又是很高的。
需要特別指出的是,如果用鍊錶來實現的話,最後任何在同乙個集合(即連通塊)中的元素,其代表指標的值都是相等的。而採用有根樹來實現的話,演算法結束後,留下的依然是樹的關係,因此如果希望每個元素都指向它的根的話,還需要對每個節點進行一次find操作,這樣每個節點的父節點都是代表此集合的節點。在某些統計問題中,往往需要這樣做。
2. kruskal最小生成樹演算法
此經典演算法的思想是將樹上的邊按照邊權排序,然後從小到大分析每一條邊,如果選到一條邊e=(v1,v2),且v1和v2不在乙個連通塊中,就將e作為最小生成樹的一條邊,否則忽略e。這其中明顯就包含了並查集的演算法。kruskal演算法也只有在結合了並查集後才能說是個高效的演算法。
並查集演算法筆記
並查集是一種用來管理元素分組情況的資料結構,並查集可以高效的進行如下操作 使用樹狀結構來實現 在樹形資料結構中,如果發生了退化的情況,複雜度就會變得很高。在並查集中,可以按照如下方法避免退化 並查集複雜度平均下來每次查詢和合併的複雜度都是常數的 阿克曼函式 leetcode 547.friend c...
演算法筆記 並查集
先給出源 和輸入輸出測試結果 includeusing namespace std const int n 110 int father n 存放父親節點 bool isboot n 標記時否為根節點 int findfather int x return x void union int a,in...
《演算法筆記》9 6 並查集
一.定義 實現 int father n father i j 表示 j 的父親結點是 i father i i 表示元素 i 是該集合的根結點 二.基本操作 1.初始化 for int i 1 i n i father i i 2.查詢 非遞迴實現 int findfather int x 遞迴實...