並查集

並查集的原理介紹

並查集是一種特殊的集合,它包括「並」和「查」兩部分,就是說,它只能進行「並」和「查」兩種操做。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;
}
相關文章
相關標籤/搜索