并查集(集合问题)

        这篇文章只是针对于初学者的简单入门,后续会不定期深入更新一些高级数据结构的知识,结合例题帮助各位码友理解。

并查集简介

        并查集(DisjointSet)是一种精巧而实用的数据结构,它主要用于处理一些不相交集合的合并问题。经典的应用有连通图、最小生成树Kruskal算法、最近公共祖先(LeastCommonAncestors,LCA)等。并查集在算法竞赛中极为常见。

        通常用“帮派”的例子说明并查集的应用场景。一个城市中有n个人,他们分成不同的帮派。同属于一个帮派的人相互之间是朋友,朋友的朋友是朋友。给出一些人的关系,如1号和2号是朋友,1号和3号也是朋友,那么他们都属于一个帮派。在分析完所有朋友关系之后,问有多少帮派,每人属于哪个帮派。给出的n=10^6。

        我们可以先思考暴力法以及复杂度。如果用并查集实现,不仅代码很简单,而且查询的复杂度小于O(log2^n)。

        并查集的概念:将编号分别为1~n的n个对象划分为不相交集合,在每个集合中,选择其中某个元素代表所在集合。

并查集的基本操作

1.初始化

        定义数组s[ ],s[i]是元素i所属的并查集,开始时,还没有处理点与点之间的朋友关系,所以每个点属于独立的集,直接以元素i的值表示它的集s[i],如元素1的集s[1]=1。如图所示,左图给出了元素与集合的值,右图画出了逻辑关系。为了便于讲解,左图区分了节点i和集s,把集的编号加上了下画线;右图用圆圈表示集,用方块表示元素。

 

2.合并

(1)加入第1个朋友关系(1,2)。在并查集s中,把节点1合并到节点2,也就是把节点1的集1改为节点2的集2,如图所示。

(2)加入第2个朋友关系(1,3)。查找节点1的集2,再递归查找节点2的集2,然后把节点2的集2合并到节点3的集3。此时,节点1、2、3都属于一个集。如图所示,为简化图示,把节点2和集2画在了一起。

(3)加入第3个朋友关系(2,4)。结果如图所示,请大家自己分析。

3.查找

        上述步骤中已经有查找操作。查找元素的集,是一个递归的过程,直到元素的值和它的集相等,就找到了根节点的集。可以看到,这棵搜索树的高度可能很大,复杂度为O(n),变成了一个链表,出现了树的退化”现象。

4.统计

        如果,s[i]=i,这是一个根节点,是它所在的集的代表;统计根节点的数量,就是集的数量。下面用一道蓝桥杯国赛真题《合根植物》给出并查集基本操作的代码。

        具体样例说明还请各位移步至官网参考,在此就不过多赘述。

        这道题如果在没有掌握并查集的前提下来做,还是比较复杂的,我也是学过很久之后没有什么印象了,就先试了一下普通解法,但做起来确实很麻烦。但如果有并查集的思想,那么这道题就很简单了。

        不会并查集的同学可以学习一下,它在算法竞赛中还是很常见的,连通图、最小生成树、LAC等等都会应用到并查集。

        这道题中主要的思路就是对于每一次给出的两个需要合根的植物,都将前一个植物所属的集合标号修改为后一个植物所属的集合,或者说是将前一个植物的集合指向后一个植物的集合,对于所有数据这样操作,最终我们可以得到若干棵搜索树,这些搜索树的高度可能会非常高,但最终都会有一个根节点,而且这个根节点的标号和他对应的植物的序号一定是相等的,我们最后只需要遍历所有植物所属的集合,找到集合标号和植物序号相等的数量,这就是合根后得到的合根植物的数量。

        虽然说我们这里将合根后的结果叫做搜索树,但其实将它视作链表理解起来会容易很多,即每一个植物的标号都是数据域,集合则是指针域,指向这个数据域所属的集合。

#include<bits/stdc++.h>
using namespace std;
const int SIZE=1000001;
int N[SIZE],ans=0;
void init_set(int s)//初始化
{
    for(int i=1;i<=s;++i) N[i]=i;
}
int find_set(int a)//查找
{
    return a == N[a] ? a : find_set(N[a]);
}
void merge_set(int a,int b)//合并
{
    a=find_set(a);  b=find_set(b);
    if(a!=b)    N[a]=N[b];//把a合并到b上,b的根成为a的根
}
int main()
{
    int m,n,k,a,b;
    cin >> m >> n >> k ;
    init_set(m*n);
    while(k--){
        cin >> a >> b;
        merge_set(a,b);//合并a,b
    }
    for(int i=1;i<=m*n;++i){//统计有多少个集
        if(N[i]==i) ans++;
    }
    cout << ans << endl;
    return 0;
}

        以上只是并查集的初步入门,后续还有合并优化、路径压缩、带权并查集等,用于解决图论中的基本问题非常方便。

        希望可以对大家有帮助。

  • 20
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值