[图论](并查集)和相应例题

一、基本概念

  • 并查集(取自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;
}

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