算法笔记:并查集

本文深入探讨了并查集这一特殊数据结构,用于解决集合元素动态管理和分组的问题。内容涵盖并查集的定义、结构、基本操作,以及如何通过路径压缩和高度合并等优化方法提高效率。同时,通过具体代码实现和实例解析,如亲戚关系判断、信仰统一问题、格子游戏等,展示了并查集在解决实际问题中的应用。
摘要由CSDN通过智能技术生成

专题:并查集

内容来源:《挑战程序设计竞赛》(第2版)

一、引入

        在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪哪个集合中。

        该问题看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往超过了空间的限制,计算机无法承受;而且复杂度较高,实现过程较复杂。因此,只能采用一种特殊数据结构——并查集来描述。

二、定义

        并查集是一种用于分离集合操作(管理元素分组情况)的抽象数据类型。它所处理的是“集合”之间的关系,即动态地维护和处理集合元素之间复杂的关系。

        当给出两个元素的一个无序对(a,b)时,需要快速“合并”a和b分别所在的集合,这其间需要反复“查找”某元素所在的集合。“并”、“查”和“集”三字由此而来。在这种数据类型中,n个不同的元素被分为若干组。每组是一个集合,这种集合叫做分离集合。

三、结构

        使用树形结构实现,不过不是二叉树。

        集合中的每个元素对应一个节点,每个集合对应一棵树。在并查集中,哪个节点是哪个节点的父亲以及树的形状等信息无需多加关注,整体组成一个树形结构才是重要的。

四、基本操作

        并查集支持查找一个元素所属的集合 以及 两个元素各自所属的集合的合并 两种操作。

1.      初始化:

        使用n个节点表示n个元素,最开始时没有边。

2.      合并:

        相当于将两个集合合并为一个集合(即求并集的过程),假定在此操作前两个集合是分离的。

3.      查询:

        查询两个节点是否属于同一集合(即他们是否有相同的根节点),我们需要沿着树向上走,来查询包含这个元素的树的根是谁。如果两个节点走到了同一个根,则可说明它们属于同一集合。

举例:


五、优化

1. 当树形结构发生“退化”,即趋于线性结构时,对并查集的基本操作的复杂度就会非常高。因此,有必要优化存储结构,想办法避免“退化”的发生。

2. 考虑高度合并的方法:对每棵树,记录树的高度rank;合并时,如果两棵树的rank不同,那么从rank小的向rank大的连边。

3. 并查集的路径压缩(一个重要且典型的方法)

        (1)路径压缩实际上是在找完根结点之后,在递归回来的时候顺便把路径上元素的父亲指针都指向根结点

        (2)以上图为例,我们在“合并5和3”的时候,不是简单地将5的父亲指向3,而是直接指向根节点1,如图:


        (3)在使用这种简化的方法时,为简便起见,即使树的高度发生了变化,也不修改rank的值。

4. 复杂度分析:O(ɑ(n)),是一个”均摊复杂度”,比O(log(n))还要快。

六、代码实现(以数组实现为例)

#include <stdio.h>
#define maxn 100005
int N,M,Q;          //建立含N个元素的集合(编号1~N),进行M次合并、Q次查找
int r1,r2,x,y;      //将x和y节点所属集合合并
int parent[maxn];   //parent[i]表示元素i的父亲节点
int rank[maxn];     //树的高度
void Init(int n)    //初始化并查集
{
    int i;
    for(i=1;i<=n;i++)//初始状态:每个节点自身为根节点,且高度为0
    {
        parent[i]=i;
        rank[i]=0;
    }
}
/*
int Find(int x)     //查询树的根(非递归实现)
{
    while(parent[x]!=x)
        x=parent[x];
    return x;       //找到返回根节点
}
*/
int Find(int x)     //查询树的根(递归实现)
{
    if(parent[x]==x)//x是根节点,找到,直接返回
        return x;
    else            //x不是根节点,则从x的父亲节点开始,继续查找
        return parent[x]=Find(parent[x]);
}
void Union(int x,int y) //合并节点x和y所属的集合
{
    x=Find
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值