並查集是一種特殊的集合,它包括「並」和「查」兩部分,就是說,它只能進行「並」和「查」兩種操做。ios
小明在玩一個叫作「到底有幾個團隊」的遊戲,這個遊戲是這樣的:有若干我的組成了若干個團隊,而後,這些人會給小明若干個線索,線索相似於說「xx和xx在一個團隊裏」,小明要作的,就是根據線索求出到底有多少個團隊。數組
好比說:優化
如今有\(3\)我的,分別是小力,小華和小剛。spa
這時他們給出了\(1\)條線索:小華和小剛是一個團隊的。3d
很顯然,有\(2\)個團隊。code
但若是再複雜一點呢?blog
如今有\(10\)我的,分別是\(1,2,3,4,5,6,7,8,9,10\)。遞歸
這時他們給出了\(7\)條線索:遊戲
\(2\)和\(4\)是一個團隊的;ci
\(5\)和\(7\)是一個團隊的;
\(1\)和\(3\)是一個團隊的;
\(8\)和\(9\)是一個團隊的;
\(1\)和\(2\)是一個團隊的;
\(5\)和\(6\)是一個團隊的;
\(2\)和\(3\)是一個團隊的。
是否是感受有點暈了,這麼多人和線索,並且這些線索繞來繞去的,實在有些難分辨。
固然,咱們能夠畫圖來幫助解題。
最初始的狀態:
獲得線索「\(2\)和\(4\)是一個團隊的」時:
獲得線索「\(5\)和\(7\)是一個團隊的」時:
獲得線索「\(1\)和\(3\)是一個團隊的」時:
獲得線索「\(8\)和\(9\)是一個團隊的」時:
獲得線索「\(1\)和\(2\)是一個團隊的」時:
獲得線索「\(5\)和\(6\)是一個團隊的」時:
獲得線索「\(2\)和\(3\)是一個團隊的」時:
由於\(2\)和\(3\)在獲得線索「\(1\)和\(3\)是一個團隊的」時就在一個團隊裏了,因此狀態沒有發生變化。
從圖中可知,共有\(4\)個團隊。
並查集就相似於模仿上文「畫圖」的方式來進行問題求解。
上文說到,並查集的操做分爲「並」和「查」兩部分,咱們來說講並查集的兩種操做的實現。
void unionn(int x,int y)//"並" { x=find(x); y=find(y); if(x!=y)father[y]=x; }
int find(int x)//"查" { if(father[x]!=x)return find(father[x]); else return x; }
int find(int x)//"查" { while(father[x]!=x)x=father[x]; return x; }
當題目數據比較特殊,好比是一條鏈時,這種「並」與「查」的方式就會超時。這時就要用到一種優化的方法:路徑壓縮。這種作法就是在找完某個元素的根節點以後,在遞歸回來的時候順便把路徑上元素的父親都指向根節點。
舉個例子:
沒有路徑壓縮的元素存儲方式:
有路徑壓縮的元素存儲方式:
從圖中能夠看出來,有路徑壓縮的並查集進行「查」的操做會更快。
實現以下(僅能遞歸實現):
int find(int x)//"查" { if(father[x]!=x)father[x]=find(father[x]); return father[x]; }
注意,最初始的狀態是全部元素的父親就是本身,因此,應當進行初始化。
代碼以下:
for(int i=1;i<=n;i++) father[i]=i;
看了那麼久,咱們來作一道題目吧,將例子中的人數設定爲\(n(n \le 10000)\)人,將線索設定爲\(m(m \le 10000)\)條,線索的輸入方式設定爲:A B
\((A,B\)爲數字\()\)如今來作一作吧。
\(Code:\)
#include <iostream> using namespace std; int father[10010];//father數組存儲着並查集元素 void start(int n)//初始化 { for(int i=1;i<=n;i++) father[i]=i; } int find(int x)//"查" { if(father[x]!=x)father[x]=find(father[x]); return father[x]; } void unionn(int x,int y)//"並" { x=find(x); y=find(y); father[x]=y; } int main() { int n,m,ans=0; cin>>n>>m; start(n); for(int i=1;i<=m;i++) { int a,b; cin>>a>>b; unionn(a,b);//將a,b合併成一個團隊 } for(int i=1;i<=n;i++) if(father[i]==i)ans++;//若是某個元素的父親就是它本身,則這是一個以其爲首的團隊 cout<<ans; return 0; }