并查集算法与应用

前言

 

并查集回炉重造已经有几天了,这次感觉理解更深刻了些,就写篇博客系统梳理一下,别忘了点赞支持下吖家人们👍👍

 

原理

 

并查集的英文是Disjoint Set Union,缩写为DSU,
直译为不相交集合,即一种不相交集,类似于树的数据结构,又因其可进行的操作有合并与查找,故中文称为并查集。

 

    1.  组成

 

并查集主要由一个整型数组和两个函数组成。

数组用于保存对象的根结点,数组下标表示对象序号,数组的值代表该对象的根结点;

一个返回值为整型的find函数,参数为待求根结点的对象序号,返回值为该的对象的根结点。

一个返回值为空的mix函数,参数为两个待合并的对象,调用后完成对两个对象的合并。

 

    2.  实现

 

并查集应用时先将各个对象分立,即一人一个不同的根结点,f[i]=i。

查找根结点时应不断调用自身,直至找到f[i]=i,合并两个对象时必须先找到各自的根结点,然后令一个对象的根结点等于另一个对象的根结点。

若遍历数组,记录下满足f[i]=i的对象的数量,此数量即为全部不相交集合的数目。

 

    3.  代码

#include<bits/stdc++.h>
using namespace std;
int f[1000000],cnt=0;
int father(int x)
{
  //return f[x]==x?x:father(f[x]);
  if(f[x]==x)return x;           //路径压缩
    return f[x]=father(f[x]);
}
void mix(int x,int y)
{ f[father(x)]=father(y);
}
int main()
{  int i,j,m,n,xx,yy;
   cin>>n>>m;
   for(i=1;i<=n;i++) //开始时各自为独立集合
    f[i]=i;
   for(i=1;i<=m;i++)
   { cin>>j>>xx>>yy;
     if(j==1) mix(xx,yy);//合并到相同集合
   }
   for(i=1;i<=n;i++)
    if(f[i]=i) cnt++;//统计根节点个数
   cout<<cnt;
}

 

 

分类

 

  • 简单并查集

多个元素,一个关系,元素间有关系则并入同一集合,没有关系则不处理。

 

  • 种类并查集

多个元素,多种关系,元素间关系可能有传递性,对立性,需建立多个并查集。

 

  • 带权并查集

类似普通并查集,只是在此基础上为元素的关系赋上权值。

 

 

应用

 

  • 简单并查集 

 

题目P3367 【模板】并查集 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

  45f89028b8014bc18cc15680f28ca604.png

 

思路

模版题,构造并查集,多次合并再遍历。

代码

#include<bits/stdc++.h>
using namespace std;
int f[1000000];
int father(int x)
{
  //return f[x]==x?x:father(f[x]);
  if(f[x]==x)return x;
    return f[x]=father(f[x]);
}
void mix(int x,int y)
{ f[father(x)]=father(y);
}
int main()
{  int i,j,m,n,xx,yy;
   cin>>n>>m;
   for(i=1;i<=n;i++)
    f[i]=i;
   for(i=1;i<=m;i++)
   { cin>>j>>xx>>yy;
     if(j==1) mix(xx,yy);
     else if(j==2)
     { if(father(xx)==father(yy))
       cout<<"Y\n";
       else cout<<"N\n";
     }
   }
}

 

  • 种类并查集

题目

https://www.luogu.com.cn/problem/P1892

c7025f80c60d4bc89ddb38c9729ac0cf.png

 

思路:开二倍数组,一组放朋友,一组放敌人。

代码

#include<bits/stdc++.h>
using namespace std;
int f[100000];
int find(int x)
{ if(f[x]==x) return x;
  return f[x]=find(f[x]);
}
void mix(int y,int x)
{
  f[find(x)]=find (y);
}
int main()
{  int n,m,p,q,i,j,k;
   char c;
   int sum=0,x,y;
   cin>>n>>m;
   for(i=1;i<=n*2;i++)
    f[i]=i;
   for(i=1;i<=m;i++)
     {  cin>>c>>x>>y;
        if(c=='F') mix(y,x);
        else {mix(y,x+n);mix(x,y+n);}
     }
   for(i=1;i<=n;i++)
    {
      if(f[i]==i) sum++;
    }
   cout<<sum;
}

 

  • 带权并查集

还没太掌握,以后一定更。。。

 

  • 连通块的并查集解决

题目https://leetcode-cn.com/problems/number-of-islands/

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETkDkuqbmnKjkuI1lbW8,size_20,color_FFFFFF,t_70,g_se,x_16

 

思路

连通块可以理解为无向图中有几个连通的点集,那么这个过程与并查集的原理就极其相似了,将点集看作并查集的祖先和他的后代们,相互连通的点就放在同一祖先下,这样只需要查找共有几个祖先即可。

代码https://leetcode-cn.com/problems/number-of-islands/solution/dao-yu-shu-liang-by-leetcode/

 

  • 最小生成树的并查集解决

 

题目P3366 【模板】最小生成树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 

f2b997e2e16540cfb4be5d172a73e6eb.png

 

思路

先把边按照权值进行排序,用贪心的思想优先选取权值较小的边,并依次连接,若出现环则跳过此边(用并查集来判断是否存在环)继续搜,直到已经使用的边的数量比总点数少一即可。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
int n,m,i,j,u,v,total;
struct edge{
    int start,to;long long val;
}bian[2000005];
int f[100000];
long long ans;

int find(int x)//并查集部分
{
    if (f[x]==x) return x; else 
    {
        f[x]=find(f[x]);
        return f[x];
    }	
}

bool cmp(edge a,edge b)//结构体快排时用到的
{
    return a.val<b.val;
}

inline void kruskal()//最小生成树
{
    
    for(int i=1;i<=m;i++)
    {
        u=find(bian[i].start);
        v=find(bian[i].to);
        if(u==v) continue;//判断在不在同一个并查集里面,在就下一个循环
            ans+=bian[i].val;//不在,就加上
            f[u]=v;//连接两个并查集
            total++;
            if(total==n-1) break;//当形成了最小生成树后,退出(之后做的也没用了)
    }
} 
int main()
{
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++) f[i]=i;
    for(i=1;i<=m;i++)
    {
        scanf("%d%d%d",&bian[i].start,&bian[i].to,&bian[i].val);
    }
    sort(bian+1,bian+m+1,cmp);//快排边长
    kruskal();
    printf("%d",ans);
    return 0;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

亦木不emo

打赏一个吧亲

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值