並查集

並查集

一、什麼是並查集?

並查集支持查找一個元素所屬的集合以外的兩個元素各自所屬的集合的合併等運算;

在一些有N個元素的集合應用問題中,我們通常是在開始時讓每個元素構成一個單元素的集合,然後按一定順序將屬於同一組的元素所在的集合“合併”,其間要反覆“查找”一個元素在哪個集合中;

“並”、“查”和“集”這三個字由此而來;

在這種數據類型中,n個不同元素被分爲若干組,每組是一個集合,這種集合叫分離集合,稱之爲並查集

這一類問題近幾年來反覆出現在信息學的國際國內賽題中,其特點是看似並不複雜,但數據量極大,若用正常的數據結構來描述的話,往往在空間上過大,計算機無法承受;即使在空間上勉強通過,運行的時間複雜度也極高,根本就不可能在比賽規定的運行時間(13秒)內計算出試題需要的結果,只能用並查集來描述。

代碼:
#include <stdio.h>
int f[1000]={0},n,m,k,sum=0;


void init()         //初始化,數組存下標編號
{
    int i;
    for(i=0;i<=n;i++)
        f[i]=i;
}


int getf(int v)     //找爹的遞歸函數,不停找爹,直到找到祖宗爲止
{
    if(f[v]==v)
        return v;
    else            //這裏是壓縮路徑,每次函數返回的時候,順帶把路上遇到的人的“boos”改爲最後找到的祖先的編號。可以提高找到祖先的速度
    {
        f[v]=getf(f[v]);
        return f[v];
    }
}


void merge(int v,int u)     //合併子集的函數
{
    int t1,t2;
    t1=getf(v);
    t2=getf(u);
    if(t1!=t2)              //判斷兩個結點是不是一個祖先
    {
        f[t2]=t1;           //“靠左原則”左邊變成右邊的boss,就是把右邊的集合作爲左邊集合的子集合
    }                       //經過路徑壓縮後,將f[u]的根的值也賦值爲v的祖先f[t1]
}


int main()
{
    int i,x,y;
    scanf("%d%d",&n,&m);
    init();         //初始化
    for(i=1;i<=m;i++)
    {
        scanf("%d %d",&x,&y);
        merge(x,y);         //合併
    }
    for(i=1;i<=n;i++)       //掃描有多少個獨立的犯罪團伙
    {
        if(f[i]==i)
            sum++;
    }
    printf("%d\n",sum);
    return 0;
}

Description

若某個家族人員過於龐大,要判斷兩個是否是親戚,確實還很不容易,給出某個親戚關係圖,求任意給出的兩個人是否具有親戚關係。 規定:xy是親戚,yz是親戚,那麼xz也是親戚。如果x,y是親戚,那麼x的親戚都是y的親戚,y的親戚也都是x的親戚。

Input

第一行:三個整數n,m,p,(n< =5000,m< =5000,p< =5000),分別表示有n個人,m個親戚關係,詢問p對親戚關係。 以下m行:每行兩個數MiMj1< =MiMj< =N,表示MiMj具有親戚關係。 接下來p行:每行兩個數PiPj,詢問PiPj是否具有親戚關係。

Output

P行,每行一個’Yes’’No’。表示第i個詢問的答案爲具有不具有親戚關係。

 

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
using namespace std;
int fa[50002],a,b,m,n,p;
/*
x代表例題中的人,fa[x]中所存的數代表這一集合中所有人都與一個人有親戚關係
相當於例題中第一個集合所有的元素都與第一個元素有親戚關係
搜索時只要找元素所指向的fa[x]=x的元素(即父元素)
然後比較兩個元素的父元素是否相同就可以判斷其關係
*/
void build(int qwq)
{
    for(int i=1;i<=qwq;i++)
        fa[i]=i;
        return ;
}//初始化,一開始每個點單獨成集合 


int find(const int &x) 
{
    return fa[x]==x?x:fa[x]=find(fa[x]);
}//找到x的最遠祖先,並且壓縮路徑
bool che(const int &x,const int &y)
{
    return find(x)==find(y);
}//判斷x,y是不是在同一個集合裏,直接判斷最遠祖先是不是一樣的 


void mer(const int &x,const int &y)
{
    if(!che(x,y)) 
        fa[fa[x]]=fa[y];
    return ;
}
//合併x,y,我們在判斷x和y是不是同一個集合裏,路徑壓縮之後fa[x],fa[y]已經是最遠祖先了,所以直接將fa[x]的父親連接起來就好 


int main()
{
    int i;
    scanf("%d%d%d",&n,&m,&p);
    build(n);
    for(i=1;i<=m;i++)
        scanf("%d%d",&a,&b),
        mer(a,b);
    for(i=1;i<=p;i++)
    {
        scanf("%d%d",&a,&b);
        if(che(a,b))
            printf("Yes\n");
        else
            printf("No\n");
    }
    return 0;
}

我感覺這類題是一個模板 XD 

最後附帶一張很有意思的圖

這個圖片的作者博客講解的比我清晰,關於路徑壓縮方面的這個博主講的也很清晰

圖片原作者傳送門 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章