[圖論](並查集)和相應例題

一、基本概念

  • 並查集(取自Union合併、Find查找、Set集合)是一種非常精巧而實用的數據結構,它主要用於處理一些不相交集合的合併問題。一些常見的用途有求連通子圖、求最小生成樹的Kruskal算法和最近公共祖先等。。。通俗地理解,朋友的朋友都是我的朋友。
  • 並查集支持下面兩個操作:
    1.合併:合併兩個集合。
    2.查找:判斷兩個元素是否在同一個集合。
    在這裏插入圖片描述
  • 查找:由於規定同一個集合中只存在一個根節點,因此查找就是對給定地結點尋找根結點的過程。
///數組f初始化:
for(int i=1;i<=n;i++)  f[i]=i;///n爲頂點個數,1到n爲頂點編號
int find(int x){///遞歸
      if(f[x]==x) return x;
      else return find(f[x]);
}

int find(int x){///非遞歸
      while(x!=f[x]) x=f[x];
      return x;
}

查找優化:路徑壓縮。把當前查詢路徑上的所有結點的父親都指向根結點,查找的時候就不需要每次都回塑去找父親了。複雜度就可以降爲O(1)了。

int find(int x){///非遞歸
     int p=x;///把x保存下來
     while(f[x]!=x) x=f[x];///尋找根節點
     while(f[p] != p){///路徑壓縮
       int t = p;
       p = f[p];///回溯父親結點
       f[t] = x;///將結點t改爲根節點x
       }
   return x;
}

int find(int x){///遞歸版
      if(x == f[x]) return x;
      else{
       int t = find(f[x]);///找根節點
       f[x] = t;
       return t;
      }
}
  • 合併:指把兩個集合合併成一個集合。只有兩個不同集合才能進行合併,保證了在同一個集合中一定不會產生環,並查集產生的每一個集合都是一棵樹
void mergex(int x,int y){
      int fa,fb;
      fa=getf(x);
      fb=getf(y);
      if(fa!=fb){
         f[fa]=fb;///f[fb]=fa同樣可以
}

合併的優化:

///a[] 記錄每個根節點對應的樹的深度(如果不是根節點,其a相當於以它作爲根節點的子樹的深度)
void mergex(int x,int y){
       if((x=find(x))==find(y)) return;
       if(a[x]>=a[y])  f[y]=x;///數組a初始化爲1
       else{
          f[x]=y;
          if(a[x]==a[y]) a[y]++;///如果深度相同且根節點不同,則新的根節點的深度+1
    }
}

二、相關例題

  • 基礎並查集: 原題地址HDUOJ
    在這裏插入圖片描述
    在這裏插入圖片描述
    題意:n個城鎮間有m條道路,爲每兩個城鎮之間都能聯通,問至少還需要修多少條道路。
#include<stdio.h>
int f[1010];
int find(int x){///找根節點
   if(x!=f[x]) f[x] = find(f[x]);
   return f[x];
}
int merge(int x,int y){
    int fx = find(x);
    int fy = find(y);
    if(fx!=fy) f[fx] = fy;
}
int main(){
    int n,m;
    while(~scanf("%d",&n)&&n){
            scanf("%d",&m);
            for(int i=1;i<=n;i++) f[i] = i;
        int u,v;
        while(m--){
             scanf("%d%d",&u,&v);
             merge(u,v);
        }
        int ans = 0;
        for(int i=1;i<=n;i++)
            if(i==find(i)) ans++;
        printf("%d\n",ans-1);///除了根結點
    }
    return 0;
}

#include<stdio.h>
#include<string.h>
int f[100010];
bool flag;
int find(int x){///找根節點
   if(x==f[x]) return x;
   else return find(f[x]);
}
int merge(int x,int y){
    int fx = find(x);
    int fy = find(y);
    if(fx!=fy) f[fy] = fx;
    else flag = true;
}
int main(){
    while(1){
    int u,v;
    int sum = 0;
    flag = false;
    memset(f,0,sizeof(f));
    while(scanf("%d%d",&u,&v)&&u&&v){
        if(u==-1&&v==-1) return 0;
        if(f[u]==0) f[u] = u;
        if(f[v]==0) f[v] = v;
        merge(u,v);
    }
    for(int i=1;i<100010;i++)
      if(f[i]==i) sum++;

    if(sum>1||flag==true) printf("No\n\n");
    else printf("Yes\n\n");
    }
    return 0;
}

  • 帶權並查集: 原題地址HDUOJ
    在這裏插入圖片描述
    題意:輸出當前的人剛成爲朋友時的社交人數,如果兩個人之前都沒有出現的話,輸出第二個的人數。
#include<stdio.h>
#include<string.h>
#include<map>
#include<string>
#include<iostream>
using namespace std;
map <string,int> ma;
int num[100010];
int f[100010];
int find(int x){
   if(f[x]!=x) f[x] = find(f[x]);
   return f[x];
}
int merge(int x,int y){
    int fx = find(x);
    int fy = find(y);
    if(fx!=fy) {
            f[fx] = fy;
            num[fy]+=num[fx];
            printf("%d\n",num[fy]);
    }
    else  printf("%d\n",num[fy]);
}
int main(){
    int t;
    while(scanf("%d",&t)!=EOF){
    while(t--){
        int n;
        scanf("%d",&n);
        ma.clear();
      for(int i=0;i<100010;i++) {
            f[i] = i;
            num[i] = 1;
      }
      string name1,name2;
      int len=1;
      for(int i=0;i<n;i++){
        ///scanf("%s %s",&name1,&name2);
        cin>>name1>>name2;
        if(!ma[name1]) ma[name1] = len++;
        if(!ma[name2]) ma[name2] = len++;
        if(name1==name2) {
            printf("%d\n",num[find(ma[name1])]);
            continue;
        }
        merge(ma[name1],ma[name2]);
      }
     }
    }
    return 0;
}

  • 種類並查集: 原題地址HDUOJ
    在這裏插入圖片描述
    題意:昆蟲分爲同性與異性,num[]==0,即f[i]==i爲同性,反之爲異性,我們用0來代表同性,1代表異性。求是否找到同性。
#include<stdio.h>
int f[2010],num[2010];
 bool flag;
int find(int x){
  if(x==f[x]) return x;
   int temp = find(f[x]);
   num[x] = (num[x]+num[f[x]])%2;
   f[x] = temp;
   return temp;
}
void merge(int u,int v){
   int fu,fv;
   fu = find(u);
   fv = find(v);
   if(fu==fv) {
     if(num[u]==num[v]) flag = false;///找到同性昆蟲
     return;
   }
   f[fv] = fu;
   num[fv] = (num[u]+1-num[v]+2)%2;
}
int main(){
   int t;
   scanf("%d",&t);
   for(int i=1;i<=t;i++){
      int n,m;
      scanf("%d%d",&n,&m);
      flag = true;
      for(int j=1;j<=n;j++){
        f[j] = j;
        num[j] = 0;
      }
      for(int j=1;j<=m;j++){
        int u,v;
        scanf("%d%d",&u,&v);
        if(flag) merge(u,v);
      }
      printf("Scenario #%d:\n",i);
      if(flag) printf("No suspicious bugs found!\n\n");
      else printf("Suspicious bugs found!\n\n");
   }
   return 0;
}

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