其實並查集顧名思義就是有「合併集合」和「查找集合中的元素」兩種操做的關於數據結構的一種算法。node
並查集算法不支持分割一個集合。ios
用集合中的某個元素來表明這個集合,該元素稱爲集合的表明元
。
一個集合內的全部元素組織成以表明元爲根的樹形結構。
對於每個元素 parent[x]指向x在樹形結構上的父親節點。若是x是根節點,則令parent[x] = x。
對於查找操做,假設須要肯定x所在的的集合,也就是肯定集合的表明元。能夠沿着parent[x]不斷在樹形結構中向上移動,直到到達根節點。算法
判斷兩個元素是否屬於同一集合,只須要看他們的表明元是否相同便可。
爲了加快查找速度,查找時將x到根節點路徑上的全部點的parent設爲根節點,該優化方法稱爲壓縮路徑。 使用該優化後,平均複雜度可視爲Ackerman函數的反函數,實際應用中可粗略認爲其是一個常數。
一、維護無向圖的連通性。支持判斷兩個點是否在同一連通塊內,和。
二、判斷增長一條邊是否會產生環:用在求解最小生成樹的Kruskal算法裏。數組
《ACM國際大學生程序設計競賽 知識與入門 俞勇主編》數據結構
通常來講,一個並查集一三個操做。函數
包括對全部單個的數據創建一個單獨的集合(即根據題目的意思本身創建的最多可能有的集合,爲下面的合併查找操做提供操做對象)
在每個單個的集合裏面,有三個東西。
1,集合所表明的數據。(這個初始值根據須要本身定義,不固定)
2,這個集合的層次一般用rank表示(通常來講,初始化的工做之一就是將每個集合裏的rank置爲0)。
3,這個集合的類別parent(有的人也喜歡用set表示)(其實就是一個指針,用來指示這個集合屬於那一類,合併事後的集合,他們的parent指向的最終值必定是相同的。)
(**有的簡單題裏面集合的數據就是這個集合的標號,也就是說只包含2和3,1省略了)。初始化的時候,一個集合的parent都是這個集合本身的標號。
沒有跟它同類的集合,那麼這個集合的源頭只能是本身了。
(最簡單的集合就只含有這三個東西了,固然,複雜的集合就是把3指針這一項添加內容,如PKU食物鏈那題,咱們還能夠添加enemy指針,表示這個物種集合的天敵集合;food指針,表示這個物種集合的食物集合。隨着指針的增長,並查集操做起來也變得複雜,題目也就顯得更難了)優化
有的人是創建一個結構體把集合表示出來,如:spa
#define MAX 10000 struct Node { int data; int rank; int parent; }node[MAX];
有的人則是弄不少相同大小的數組,如:設計
int set[max];//集合index的類別,或者用parent表示 int rank[max];//集合index的層次,一般初始化爲0 int data[max];//集合index的數據類型 //初始化集合 void Make_Set(int i) { set[i]=i;//初始化的時候,一個集合的parent都是這個集合本身的標號。沒有跟它同類的集合,那麼這個集合的源頭只能是本身了。 rank[i]=0; }
通常來講,題目簡單用數組,題目複雜用結構體,由於結構體有條理,數組能夠少打幾個字。
就是找到parent指針的源頭,能夠把函數命名爲get_parent(或者find_set,這個隨你喜歡,以便於理解爲主)
若是集合的parent等於集合的編號(即尚未被合併或者沒有同類),那麼天然返回自身編號。
若是不一樣(即通過合併操做後指針指向了源頭(合併後選出的rank高的集合))那麼就能夠調用遞歸函數,以下面的代碼:指針
/** *查找集合i(一個元素是一個集合)的源頭(遞歸實現)。 若是集合i的父親是本身,說明本身就是源頭,返回本身的標號; 不然查找集合i的父親的源頭。 **/ int get_parent(int x) { if(node[x].parent==x) return x; return get_parent(node[x].parent); }
數組的話就是:
//查找集合i(一個元素是一個集合)的源頭(遞歸實現) int Find_Set(int i) { //若是集合i的父親是本身,說明本身就是源頭,返回本身的標號 if(set[i]==i) return set[i]; //不然查找集合i的父親的源頭 return Find_Set(set[i]); }
int unifind(int a){// find the root and compress the path int root = a; //find the root while(root != parent[root] ){ // The parent of root is root itself. root = parent[root]; } // compress the path while( a != root){ int parentOfA = parent[a]; parent[a] = root; // 將當前節點的父節點直接設置爲父節點 a = parentOfA; } return root; }
這就是所謂並查集的並了。至於怎麼知道兩個集合是能夠合併的,那就是題目的條件了。
先看代碼:
void Union(int a,int b) { a=get_parent(a); b=get_parent(b); if(node[a].rank>node[b].rank) node[b].parent=a; else { node[a].parent=b; if(node[a].rank==node[b].rank) node[b].rank++; } }
再給出數組顯示的合併函數:
void Union(int i,int j) { i=Find_Set(i); j=Find_Set(j); if(i==j) return ; if(rank[i]>rank[j]) set[j]=i; else { if(rank[i]==rank[j]) rank[j]++; set[i]=j; } }
int count = 0; // the number of independent sets 即計算有多少個 parent[i] == i;
![圖片上傳中...]
AC代碼:
/* * Copyright (c) Huawei Technologies Co., Ltd. 2012-2018. All rights reserved. * Description: 項目 City Road 的源文件 * Author: c00518290 * Create: 2019-08-05 */ #include <stdio.h> #include <iostream> using namespace std; int parent[1002]; int main(){ int n,m,a,b; int count=0; scanf("%d %d",&n,&m); int list[n+1]; for(int i=1;i<=n;i++){ list[i]=i; } for(int i=0;i<m;i++){ scanf("%d %d",&a,&b); while(list[a]!=a){ a=list[a]; } while(list[b]!=b){ b=list[b]; } if(list[b]!=list[a]){ list[b]=list[a]; } } for(int i=1;i<=n;i++){ if(list[i]==i) count++; } printf("%d\n",count-1); } /** 5 2 1 2 3 5 **/
![圖片上傳中...]