新get的數(shù)據(jù)結(jié)構(gòu)——并查集

查并集是一個(gè)很重要的數(shù)據(jù)結(jié)構(gòu),特別適合解決一些類(lèi)似于圖,集合的問(wèn)題。比如這個(gè):http://acm.hdu.edu.cn/showproblem.php?pid=1232

什么是并查集?

為了解釋并查集的原理,我將舉一個(gè)更有愛(ài)的例子。 話(huà)說(shuō)江湖上散落著各式各樣的大俠,有上千個(gè)之多。他們沒(méi)有什么正當(dāng)職業(yè),整天背著劍在外面走來(lái)走去,碰到和自己不是一路人的,就免不了要打一架。但大俠們有一個(gè)優(yōu)點(diǎn)就是講義氣,絕對(duì)不打自己的朋友。而且他們信奉“朋友的朋友就是我的朋友”,只要是能通過(guò)朋友關(guān)系串聯(lián)起來(lái)的,不管拐了多少個(gè)彎,都認(rèn)為是自己人。這樣一來(lái),江湖上就形成了一個(gè)一個(gè)的群落,通過(guò)兩兩之間的朋友關(guān)系串聯(lián)起來(lái)。而不在同一個(gè)群落的人,無(wú)論如何都無(wú)法通過(guò)朋友關(guān)系連起來(lái),于是就可以放心往死了打。但是兩個(gè)原本互不相識(shí)的人,如何判斷是否屬于一個(gè)朋友圈呢?
我們可以在每個(gè)朋友圈內(nèi)推舉出一個(gè)比較有名望的人,作為該圈子的代表人物,這樣,每個(gè)圈子就可以這樣命名“齊達(dá)內(nèi)朋友之隊(duì)”“羅納爾多朋友之隊(duì)”……兩人只要互相對(duì)一下自己的隊(duì)長(zhǎng)是不是同一個(gè)人,就可以確定敵友關(guān)系了。
但是還有問(wèn)題啊,大俠們只知道自己直接的朋友是誰(shuí),很多人壓根就不認(rèn)識(shí)隊(duì)長(zhǎng),要判斷自己的隊(duì)長(zhǎng)是誰(shuí),只能漫無(wú)目的的通過(guò)朋友的朋友關(guān)系問(wèn)下去:“你是不是隊(duì)長(zhǎng)?你是不是隊(duì)長(zhǎng)?”這樣一來(lái),隊(duì)長(zhǎng)面子上掛不住了,而且效率太低,還有可能陷入無(wú)限循環(huán)中。于是隊(duì)長(zhǎng)下令,重新組隊(duì)。隊(duì)內(nèi)所有人實(shí)行分等級(jí)制度,形成樹(shù)狀結(jié)構(gòu),我隊(duì)長(zhǎng)就是根節(jié)點(diǎn),下面分別是二級(jí)隊(duì)員、三級(jí)隊(duì)員。每個(gè)人只要記住自己的上級(jí)是誰(shuí)就行了。遇到判斷敵友的時(shí)候,只要一層層向上問(wèn),直到最高層,就可以在短時(shí)間內(nèi)確定隊(duì)長(zhǎng)是誰(shuí)了。由于我們關(guān)心的只是兩個(gè)人之間是否連通,至于他們是如何連通的,以及每個(gè)圈子內(nèi)部的結(jié)構(gòu)是怎樣的,甚至隊(duì)長(zhǎng)是誰(shuí),并不重要。所以我們可以放任隊(duì)長(zhǎng)隨意重新組隊(duì),只要不搞錯(cuò)敵友關(guān)系就好了。于是,門(mén)派產(chǎn)生了。

0_1311901712oy9f.gif.jpg
并查集的優(yōu)化:路徑壓縮
0_131190167189S8.gif.jpg

這樣可以將查詢(xún)一個(gè)結(jié)點(diǎn)的根節(jié)點(diǎn)的時(shí)間復(fù)雜度降到O(1)。


實(shí)現(xiàn)
#include <iostream>
using namespace std;

//查找根節(jié)點(diǎn)
int find(int * pre,int length,int v){
    int _v=v;
    int root;

    if(pre[_v]==_v){
        return v;
    }

    while(pre[_v]!=_v){
        _v=pre[_v];
    }
    root=_v;  

    //順便完成路徑壓縮
    _v=v;
    while(pre[_v]!=_v){
        int temp=pre[_v]; // 在改變上級(jí)之前用臨時(shí)變量  j 記錄下他的值
        pre[_v]=root;//把上級(jí)改為根節(jié)點(diǎn)
        _v=temp;
    }
    return root; //返回根節(jié)點(diǎn) r
}

//判斷v1 v2是否連通
//如果已經(jīng)連通,就不用管了,如果不連通,就把它們所在的連通分支合并起,將v2合并到v1里面
void join(int * pre,int length,int v1,int v2){
    int root_v1=find(pre,length,v1);
    int root_v2=find(pre,length,v2);

    if(root_v1==root_v2){
        return ;
    }else{
        pre[v2]=root_v1;
    }
}


void print(int * pre,int length){
    for(int i=1;i<length;i++){
        cout<<pre[i]<<"   ";
    }
    cout<<endl;
}

int main(){
    const int vertex=4;
    const int n=vertex+1;
    int pre[n];

    //初始化
    for(int i=1;i<n;i++){
        pre[i]=i;
    }

    print(pre,n);
    join(pre,n,1,2);
    join(pre,n,3,4);
    print(pre,n);

    //判斷有幾個(gè)根節(jié)點(diǎn)
    int arr_root[n];
    //初始化為0
    for(int i=0;i<n;i++){
        arr_root[i]=0;
    }

    //將是根節(jié)點(diǎn)的點(diǎn)置為1
    for(int i=1;i<n;i++){
        arr_root[find(pre,n,i)]=1;
    }

    //統(tǒng)計(jì)一下置為1的結(jié)點(diǎn)的個(gè)數(shù)
    int ans=0;
    for(int i=1;i<n;i++){
        if(arr_root[i]==1){
            ans++;
        }
    }

    cout<<ans<<endl;

    system("pause");
    return 0;
}

關(guān)于數(shù)據(jù)結(jié)合和算法的其他文章:
這又是一個(gè)flag http://m.itdecent.cn/p/1dc543da3897

update:
在論壇上面看見(jiàn)的一個(gè)問(wèn)題,用并查的思想可以很快的算出來(lái)。


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容