并查集详解

定义 

并查集(Disjoint Set),又称为不相交集合或并查集合并数据结构。它是一种用于管理元素所属集合的数据结构,实现为一个森林,其中每棵树表示一个集合,树中的节点表示对应集合中的元素。

它支持以下两种主要操作:

  1. 合并(Union):将两个不相交的集合合并成一个集合,通常使用集合的根节点来表示集合。
  2. 查找(Find):查找元素所属的集合,通常返回集合的根节点。

基于数组实现并查集

首先,创建与元素个数相同大小的数组,每个数组下标与每个元素一一对应,数组下标表示当前元素,对应的值表示当前元素的父节点。初始时,将每个元素看作一个集合。下标与父节点值相同代表为这个集合的根节点。

合并

将两个元素所属的集合合并成一个,其实就是找到两个元素的根节点,然后将其中一个根节点的父节点指向 另一个根节点即可:

void union(int x, int y)
{
	int fx = find(x);
	int fy = find(y);
	if (fx == fy)
		return;
	parent[fy] = fx;
}

查找

在进行查询操作时,只需要查看两个元素的根节点是否一致,就能知道两个元素是否属于同一个集合。

根节点特点是其父节点指向自身。想要查找元素1的根节点,现在数组中查找元素1对应的父节点0,在查找元素0的父节点,直到父节点与自身相同。

void find(int x){
    return parent[x]==x?x:find(parent[x]);
}

优化

在集合很大或者树很不平衡时,树会退化成一条链,单次查询的时间复杂度高达O(n)。

路径压缩(Path Compression)

在从底向上查找根节点过程中,如果此时访问的节点不是根节点,则我们可以把这个节点尽量向上移动一下,从而减少树的层树。这个过程就叫做路径压缩。

在查找3的根节点时,我们将3到根节点的路径上经过的节点都指向根节点。

int find(int x)
{
    return parent[x]==x?x:parent[x]=find(parent[x]);
}

按秩合并(Union By Rank)

合并时,选择哪棵树的根节点作为新树的根节点会影响未来操作的复杂度。

按秩合并指的是在每次合并操作时,都把「秩」较小的树根节点指向「秩」较大的树根节点。

这里的「秩」有两种定义,一种定义指的是树的深度;另一种定义指的是树的大小(即集合节点个数)。无论采用哪种定义,集合的秩都记录在树的根节点上。

按照树的节点个数来合并:把「集合节点个数」较少的树根节点指向「集合节点个数」较大的树根节点。

按深度合并:在每次合并操作时,都把「深度」较小的树根节点指向「深度」较大的树根节点。

单独用一个数组 rank记录每个根节点对应的集合节点个数(如果不是根节点,其 rank 值相当于以它作为根节点的子树的集合rank)。

初始化时,将所有元素的 rank 值设为 1。在合并操作时,比较两个根节点,把 rank 值较小的根节点指向 rank值较大的根节点上合并。

void merge(int x, int y)
{
	int fx = find(x);    //找出元素对应的根节点
	int fy = find(y);
	if (fx == fy)    //根节点相同
		return;

	if (rank[fx] > rank[fy]) {   //x集合的秩大于y集合的秩
		parent[fy] = fx;    //将y集合的根节点的父节点指向x集合的根节点

        //rank[fx]+=rank[fy];    按大小合并需要加上另一个集合的节点数量

    }
	else if (rank[fx] < rank[fy]){
		parent[fx] = fy;
        
        //rank[fy]+=rank[fx];
    }
	else{    
		parent[fy] = fx;

        rank[fx]++; 深度相同的话,需要加一层

        //rank[fx]+=rank[fy]; 按大小合并
    }
}

引入路径压缩之后,维护真实的深度或者集合元素个数就会变得比较难。为了方便,可以只使用路径压缩。

应用

并查集通常用来求解不同元素之间的关系问题,比如判断两个人是否是好友、两个点之间时候存在至少一条路径连接。或者用来求解集合的个数、集合中元素的个数等等。

  • 15
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值