并查集笔记

这篇具有很好参考价值的文章主要介绍了并查集笔记。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

并查集

导论

并查集是一种数据结构,主要用于处理一些不相交集合的合并问题。一般应用在连通图、最小生成树、Kruskal算法、最近公共祖先(LCA)等算法中。

举例

用帮派例子理解并查集:在n个人中,分成了不同的帮派,每个帮派的人都互为朋友,朋友的朋友是朋友,例如1号和2号是朋友,1号和3号也是朋友,那么2号和3号也是朋友,问每个人属于哪个帮派?

概念

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

主要内容

并查集的基本操作、并查集的合并优化、并查集的查询优化——路径压缩、(按秩合并——不常用)、带权并查集。

一、并查集的基本操作

1.初始化

用每个点代表独立的集。

并查集笔记

2.合并

先把(1,2)合并,在合并(2,3),以此类推若合并所有点他们的最终节点就是最后一个合并的数所指向的节点,我们称这个节点为根节点,合并操作就是把每个集合归为一个根节点。

并查集笔记

3.查找

通过一个节点查找他的根节点,查找是通过递归进行的,最坏情况是查找的节点是头节点而目标是尾节点那么时间复杂度就是O(n)了(类似链表的查找操作)。

4.统计

如果s[i]=i,这是一个根节点,是他所在集合的代表;统计根节点的数量就是集合的数量。

5.例题

[hdu 1213](问题 - 1213 (hdu.edu.cn))

并查集笔记

代码模板

#include <bits/stdc++.h>
using namespace std;
const int N= 1010;
int s[N];
int cnt;

void init_set()               //初始化
{
    for(int i=1;i<N;i++)s[i]=i;
}

int find_set(int x)           //查找
{
    return x==s[x]?x:find_set(s[x]);
}

void merge_set(int x,int y)   //合并
{
    x=find_set(x),y=find_set(y);
    if(x!=y)s[x]=s[y];        //把s[y]的值赋给s[x],即s[x]指向s[y]
}

int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        init_set();
        int n,m;
        cin>>n>>m;
        for(int i=0;i<m;i++)
        {
            int x,y;
            cin>>x>>y;
            merge_set(x,y);
        }
        cnt=0;
        for(int i=1;i<=n;i++)
            if(s[i]==i)cnt++;   //查找集合
        cout<<cnt<<endl;
    }
}

二、合并的优化

步骤

基本操作是把两个集合合并位一个长集合,即每一个点只有一个子节点,在最坏情况下复杂度都为O(n)。

可以通过引入多叉树的概念减小树的长度把复杂度优化到O(n)。

并查集笔记

我们可以发现这样优化后一个节点上可以有多个分枝,在大量数据时可以有效减少查询、合并的耗费的时间(相比于朴素版)。

在合并时我们可以发现如果将大树合并到小树上去那么大树的长度会加一,如果把小树合并到大树上去,最大长度还是大树的长度,

即把小树合并到大树上可以减少整颗树的长度。

代码模板

int hight[N];
void init_set()
{
    for(int i=1;i<N;i++)s[i]=i;
}
void merge_set(int x,int y)
{
    x=find_set(x),y=find_set(y);
    if(x!=y)
    {
        if(hight[x]==hight[y])
        {
            hight[y]=hight[y]+1;
            s[x]=s[y];
        }
        else
        {
            if(hight[x]<hight[y])s[x]=s[y];
            else s[y]=s[x]; 
        }
    }
}

三、路径压缩

优化步骤

由于我们对算法都是精益求精的,想在合并的优化上进一步优化,有些聪明的小朋友就想到了一个办法把递归后的回溯也用上,就出现了路径压缩的代码。这个优化的强大在于在每次查询后都会把沿途上的点指向的节点更新为跟节点,这样我们的合并就只需要把要合并的点直接指向根节点,而且由于每个点都是指向他们的根节点所以查询的复杂度为O(1),不得不说优化这个代码的小朋友太厉害了。

并查集笔记

代码模板.1

int find_set(int x)
{
    if(x!=s[x])s[x]=find_set(s[x]);
    return s[x];
}

如果数据规模太大引起爆栈,这可以把栈换为循环

代码模板.2

int find_set(int x)
{
    int r=x;
    while(s[r]!=r)r=s[r];   //查找根节点
    int i=x,j;				//j是用户记录s当前值的桥梁和冒泡排序中的桥梁作用一样
    while(i!=r)
    {
        j=s[i];
        s[i]=r;
        i=j;
    }
	return r;
}

通过先查找到根节点,找到了根节点的值,在重头开始遍历把每个点的指向都改为根节点。

四、带权并查集

上述的操作中只讲述了并查集处理集合的问题——判断集合的归属关系,很多时候集合往往会附带一些独特的权值,独特在于有些权值是通过某种建模从具体问题中抽象出来的某种属性,在问题中需要维护这种属性才能得到应有的答案,而这种抽象能力需要大量做题才能培养出来。

并查集实际上是在维护若干颗树。并查集的合并和查询优化实际上是在改变树的形状,把原来‘’细长‘’的、操作效率低的‘’小树‘’,变为‘’粗短‘’的、操作效率高的少量‘’大树‘’。如果在原来的‘’小树‘’上,点之间有权值,那么经过并查集的优化变成‘’大树‘’后,这些权值的操作也变得高效了。

定义一个权值数组d[],把节点i到父节点的权值记为d[i]。

1.带权值的路径压缩

并查集笔记

以权值相加操作为例,d[x]代表到父节点x的权值,通过递归到根节点,d[x]就表示到根节点的权值。

代码模板

int find_set(int x)
{
    if(x!=s[x])
    {
        int t=s[x];
        s[x]=find_set(s[x]);
        d[x]+=d[t];
    }
    return s[x];
}

用递归记录当前节点的父节点,回溯是d[x]已经指向跟节点,再通过之前记录的t把路径上的权值相加。

2.带权值的合并

在合并操作中,把x和y合并,就是把x的根节点f(x)合并到y的根节点f(y)。在f(x)和f(y)之间加上权值,这个权值要符合题目要求。

3.例题

[hdu-3038](Problem - 3038 (hdu.edu.cn))

并查集笔记

分析题目

题目大意,在[1,n]这个范围中,给出m调指令,遇到冲突就忽略掉,问一共忽略了几条。

需要注意的是,题目指出如果没有逻辑错误答案都是正确的,如果给出[1,3]=100、[1,2]=200并不冲突,因为权值可以为负数。

思路:我们只需要判断所求区间在已知区间(可以计算得出的区间)内冲突才忽略,权值存在负数就可以用矢量来表示,即可以用向量法。

对于求两段之间的距离参考前缀和的区间距离计算公式是b-(a-1),所以要把a的值-1,如果不-1的话计算的距离会把a本身的值减去得到的就是a+1~b的距离。

并查集笔记

只有两个点同属于一个集合时才没有v(ra),这时可以计算出剩余区间的值,如果这个值与给定的值相同则成立,反之则冲突。

对于区间权值的合并就交给并查集完成,因为相加的权值所以直接套模板就行。

代码模板

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int s[N];
int d[N];
int ans;

void init()
{
    for(int i=0;i<N;i++){s[i]=i;d[i]=0;}
}

int find(int x)
{
    if(x!=s[x])
    {
        int t=s[x];
        s[x]=find(s[x]);
        d[x]+=d[t];
    }
    return s[x];
}

void merge(int a,int b,int v)
{
    int ra=find(a),rb=find(b);
    if(ra==rb)
    {
        if(d[a]-d[b]!=v)ans++;    
    }
    else
    {
        s[ra]=rb;
        d[ra]=d[b]-d[a]+v;
    }
}

int main()
{
    int n,m;
    while(cin>>n>>m)
    {
        ans=0;
        init();
        for(int i=0;i<m;i++)
        {
            int x,y,v;
            cin>>x>>y>>v;
            x--;
            merge(x,y,v);
        }
        cout<<ans;
    }
    return 0;
}

[poj-1182](1182 -- 食物链 (poj.org))

并查集笔记

这一题的权值是从题目中抽象出来的一种属性,这个属性可以表示两个动物的关系,而这个属性的难点在于找出权值的更新关系。

并查集笔记

合并并查集是类似上一题的做法,把权值抽象为距离关系,找到两个值同属于一个集合计算出的值与既定的值矛盾即可忽略

代码模板

#include <bits/stdc++.h>
using namespace std;
const int N=5e4+10;
int s[N];
int d[N];
int ans;

void init()
{
    for(int i=1;i<N;i++)s[i]=i;
}

int find(int x)
{
    if(x!=s[x])
    {
        int t=s[x];
        s[x]=find(s[x]);
        d[x]=(d[x]+d[t])%3;
    }
    return s[x];
}

void merge(int x,int y,int v)
{
    int rx=find(x),ry=find(y);
    if(rx==ry)
    {
        if((d[x]-d[y]+3)%3!=v)ans++;
    }
    else
    {
        s[rx]=ry;
        d[rx]=(d[y]-d[x]+v)%3;
    }
}

int main()
{
    int n,k;
    cin>>n>>k;
    init();
    while(k--)
    {
        int d,x,y;
        cin>>d>>x>>y;
        d--;                                   //在题目中1是同类,2是吃,我们规定的是0是同类1是吃。
        if(x>n||y>n||(x==y&&d==1))ans++;
        else merge(x,y,d);
    }
    cout<<ans;
    return 0;
}

五、习题集

poj

2524/1611/1703/2236/2492/1988/1182

hud

3635/1856/1272/1325/1198/2586/6109

洛谷

P1111/P3958/P1525/P4185/P2024/P1197/P1196/P1955

声明

本文是《算法竞赛》的笔记,仅此而已。文章来源地址https://www.toymoban.com/news/detail-560050.html

到了这里,关于并查集笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】--并查集

    目录 一、概念 ​编辑 二、应用场景--“连接”问题(属于同一Qu 三、实现思路  四、如何存储数据 五、定义接口 1.初始化(init) 2.其他 isSame() 六、抽象类 六、Quick Find【v1 所在集合的所有元素都指向 v2 的根节点】 1.Union 1.Union图解 2.注意点:  3.代码实现 2.find  1.find图

    2023年04月09日
    浏览(25)
  • 高阶数据结构 ——— 并查集

    并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。 并查集通常用森林来表示,森林中的每棵树表示一个集合,树中的结点对应一个元素。 说明一下: 虽然利用其他数据结构也能完成不相交集合的合并及查询,但在数据量极大的情况下,其耗费的时

    2024年02月03日
    浏览(40)
  • 数据结构之并查集

    并查表原理是一种 树型的数据结构 ,用于处理一些不相交集合的合并及查询问题。并查集的思想是用一个数组表示了整片森林(parent),树的根节点唯一标识了一个集合,我们只要找到了某个元素的树根,就能确定它在哪个集合里。这类问题的抽象数据类型称为并查集(uni

    2024年02月12日
    浏览(30)
  • 【数据结构与算法】并查集

    并查集是一个树形结构,所谓的并查,就是 当我们有了一个节点,我们就能知道这个节点属于哪个集合 。 举个例子理解以下:战国时期有很多国家,它们会互相打仗,那么现在有两个互相不认识的人,如何知道对方是敌是友呢? 现在有一种方法:每个国家都有一个大王,我

    2023年04月15日
    浏览(28)
  • 算法与数据结构(九)--并查集

    1.处理对象:Disjoint Set,即“不相交集合”。 在一些应用问题中,需将n个不同的元素划分成一组不相交的集合。开始时,每个元素自成一个单元素集合,然后按一定顺序将属于同一组元素的集合合并。其间要反复用到查询某个元素属于哪个集合的运算。适合于描述这类问题的

    2024年02月10日
    浏览(31)
  • 【数据结构】| 并查集及其优化实现

    以一个直观的问题来引入并查集的概念。 亲戚问题:有一群人,他们属于不同家族,同一个家族里的人互为亲戚,不同家族的人不是亲戚。随机指定两个人,问他们是否有亲戚关系。 以下图3个不相交的集合表示 3 个家族,当询问两个人是否有亲戚关系时,也就是问两个元素

    2024年02月09日
    浏览(27)
  • 计算机基础--->数据结构(9)【并查集】

    并查集是一种用于解决集合合并和查询问题的数据结构,主要用于实现有关集合的操作,它有两种主要操作,合并(union)和查找(find)。 查找(Find):用来确定元素属于哪个集合。它接受一个元素作为参数,并返回这个元素所属集合的代表元素。通过查找操作,可以判断

    2024年02月15日
    浏览(40)
  • 数据结构(八):并查集详解 (多图+动图)

    目录 一、什么是并查集 二、并查集的存储结构 三、并查集的基本操作 (一)初始化 (二)Find操作 (三)Union操作 四、并查集的优化 (一)Union操作优化(小树并入大树) (二)Find操作优化(压缩路径)         并查集的逻辑结构是一个包含N个元素的 集合 ,如图:

    2024年02月03日
    浏览(36)
  • 数据结构--并查集的进一步优化

    压缩路径 − − F i n d 操作,先找到根节点,再将查找路径上所有结点都挂到根结点下 color{red}压缩路径 -- Find操作,先找到根节点,再将查找路径上所有结点都挂到根结点下 压缩路径 − − F in d 操作,先找到根节点,再将查找路径上所有结点都挂到根结点下 每次Find操作,

    2024年02月15日
    浏览(41)
  • Java高阶数据结构 & 并查集 & 最小生成树

    并查集与最小生成树 1.1 并查集的原理 在一些应用问题中,我们常常会遇到一类问题 一开始是一个人 后来新增的人可能与这个人有关系,也可能与这个人无关系。 一个人与一个人有关系,这个人与另一个人也有关系,那么三人都有关系。 有关系的和没关系的之间是不同的类

    2024年02月03日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包