1. 程式人生 > 其它 >2020-12-26

2020-12-26

技術標籤:資料結構

並查集的使用

並查集主要用於解決一些元素分組的問題。它管理一系列不相交的集合,並支援兩種操作:

  • 合併(Union):把兩個不相交的集合合併為一個集合。
  • 查詢(Find):查詢兩個元素是否在同一個集合中。

當然,這樣的定義未免太過學術化,看完後恐怕不太能理解它具體有什麼用。所以我們先來看看並查集最直接的一個應用場景:親戚問題。

(洛谷P1551)親戚題目背景
若某個家族人員過於龐大,要判斷兩個是否是親戚,確實還很不容易,現在給出某個親戚關係圖,求任意給出的兩個人是否具有親戚關係。
題目描述
規定: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個詢問的答案為“具有”或“不具有”親戚關係。~

這其實是一個很有現實意義的問題。我的思路是我們可以建立模型,把所有人劃分到若干個不相交的集合中,每個集合裡的人彼此是親戚。為了判斷兩個人是否為親戚,只需看它們是否屬於同一個集合即可。因此,這裡就可以考慮用並查集進行維護了。這是一道很簡單的模板題,下面是我的程式碼

inline void init(int n){
 for(int i=1;i<=n;i++){
  fa[i]=i;
  rank[i]=1;
 }
}//假如有編號為1, 2, 3, ..., n的n個元素,我們用一個數組fa[]來儲存每個元素的父節點(因為每個元素有且只有一個父節點,所以這是可行的)。
//一開始,我們先將它們的父節點設為自己。
int find(int x){
 return x==fa[x]?x:(fa[x]=find(fa[x]));
}//我們用遞迴的寫法實現對代表元素的查詢:一層一層訪問父節點,直至根節點(根節點的標誌就是父節點是本身)。
//要判斷兩個元素是否屬於同一個集合,只需要看它們的根節點是否相同即可。
inline void merge(int i,int j){
 int x=find(i),y=find(j);
 if(rank[x]<=rank[y]){
  fa[x]=y;
 }else{
  fa[y]=x;
 }
 if(rank[x]==rank[y]&&x!=y){
  rank[y]++;
 }
}//我們應該把簡單的樹往復雜的樹上合併,而不是相反。因為這樣合併後,到根節點距離變長的節點個數比較少。   
// 我們用一個數組rank[]記錄每個根節點對應的樹的深度(如果不是根節點,其rank相當於以它作為根節點的子樹的深度)。
//一開始,把所有元素的rank(秩)設為1。合併時比較兩個根節點,把rank較小者往較大者上合併。路徑壓縮和按秩合併如果一起使用,時間複雜度接近  ,但是很可能會破壞rank的準確性。   
// 值得注意的是,按秩合併會帶來額外的空間複雜度,可能被一些卡空間的毒瘤題卡掉。

一般的情況下我們採用按秩合併是最好的選擇。
還有一道題大家可以去思考思考。洛谷P3958 乳酪

第一次寫部落格不太會寫所以借鑑了一下其他的部落格加上我自己的東西,大家要是有疑問請評論,會給大家解答的,謝謝瀏覽。