并查集

1.前言

1.1并查集的概念

并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。常常在使用中以森林来表示。–百度百科

1.2并查集的引入

在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中。其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。

1.3并查集的主要操作

  • 合并
  • 查询
    通过主要操作也就不难理解并查集名字的由来——对于集合元素的查询,以及对于集合的合并。称作并查集。

2.并查集的代码实现

2.1初始化

我们定义一个fa数组,fa数组用来存储下标元素的父亲结点。
对其进行初始化。在开始每一个元素的父亲结点就是它本身(我父亲竟是我自己 )qaq。

int fa[10000]
for (int i = 1;i <= 9999;i++)
	fa[i]=i;

2.2合并

合并操作其实就是更改fa数组中存的内容。
比如合并1和2,就是让fa[1]=2;
这并不是完整的合并操作,下面讲完查询操作会做解释。
代码实现

void merge(int a, int b)
{
	fa[a]=b;
}

2.3查询

在查询操作中,其实要查询的是每个元素的祖先结点(如果只是要查询父亲结点可以直接输出fa[x]),如果fa[x]等于x那么x就是根节点(在没有合并之前的每一个点都是根节点)如果fa[x]!=x那么就查询fa[x]的父亲的父亲。所以其实要用的就是递归。
代码实现

int hp(int x)
{
	if(fa[x]==x)
		return x;
	return hp(fa[x]);

有的人可能会发现这个算法并不节省时间,因为每一次查询都要往上不断的查询。如果我有q次查询,每一次都查询树的深度n,那么它的时间复杂度就是q*n。如果q=n,那么时间复杂度就是O(n^2)。这并不够优化。
甚至可以说是跟没用一样
那么该怎么优化呢。。。
就是要在查询的过程中不断更新,
即每次查询都要让fa[x]=hp(fa[x])。
其实就是套娃就是原本只是储存x的父亲结点的fa[x]更新为储存fa[x]的父亲结点,在每一次递归时,最后都会把当前路线上的所有元素的fa[x]的值更新为根节点。
什么意思呢?就是比如说现在有这么一条路径(1是根节点)
1-2-3-4-5
查询5的祖先,开始我们发现fa[x]=4!=x=5,那么我们要递归查询fa[x],递归时我们return fa[x]=hp(fa[x]).
第一次递归,我们发现fa[x]=3!=x=4,继续return fa[x]=hp(fa[x]).
第二次递归,我们发现fa[x]=2!=x=3,继续return fa[x]=hp(fa[x]).
第三次递归,我们发现fa[x]=1!=x=2,继续return fa[x]=hp(fa[x]).
第四次递归,终于fa[x]=x=1。返回x的值1.
退回第三次递归,fa[2]=1.
退回第二次递归,fa[3]=1.
退回第四次递归,fa[4]=1.
返回函数当中fa[5]=1.
最后的返回值为1.
5的祖先是1.

int hp(int x)
{
	if(fa[x] == x) return x;
	else {
		return fa[x] = hp(fa[x]);
	}

2.4操作的结合

上面对于初始化,合并,以及查询操作都进行了单独的解释。
那么把他们组合到一起该怎么做呢?
首先初始化是肯定要做的否则可能会找不到祖先
然后就是查询与合并操作。
上面说到合并操作并不完整,这里做出解释。
首先还是按照上面写的写好查询操作。
现在要写合并操作了。
请大家回想一下,刚刚写查询操作的时候,我们是不是对于fa数组在查询过程中进行了更新。那么合并操作是否也需要更新呢?我们再来看一看代码。

void merge(int a, int b)
{
	fa[a]=b;
}

a与b合并的时候,只是让fa[a]=b。但是如果此时我们要查询a的祖先的时候,如果fa[b]!=b,我们还需要继续向上查询,并且更新。所以在合并的时候也是要进行更新的。

void merge(int a, int b)
{
	fa[hp(a)]=hp(b);
}

就是对与a的祖先结点进行变更为b的祖先结点,就可以把他们并入一个集合。
并查集的完整代码

#include <bits/stdc++.h>
int fa[10000];
int hp(int x)
{
	if(fa[x] == x) return x;
	else {
		return fa[x] = hp(fa[x]);
	}
}
int marage(int x,int y)
{
	fa[hp(x)] = hp(y);
}
int main(){
	for (int i = 0; i <= 10000; i++)
		fa[i] = i;
	return 0;
}

根据不同的题对于并查集的内容可能会有更改,以及不同的应用。

3例题演练

3.1并查集模板题

题目来源洛谷P3367
没有什么好说的就是一道简单的模板题

3.2亲戚

题目来源洛谷P1551

3.3朋友

洛谷P2078

题意两个公司,一个公司全是男的,一个公司全是女的。

给你一些关系表示是朋友。

问你男公司与小明(用1表示)是朋友关系的人,与女公司小红(用-1表示)是朋友关系的人可以凑成多少对情侣。

男用正数表示,女用负数表示。

题解用并查集合并之后,再查询。

就这样去写吧,交了之后wrong answer,或者run time error.

那是为什么呢?

注意数组中下标没有用负数表示的。

人傻了,找到了方法,不知道该怎么处理了。

可以变通一下嘛QAQ。。。

对于负数表示的,我们用n+1到n+m表示就可以了!(给负数*-1+n)

处理好关系之后,分别统计对于小明,hp(x)==fa[1]有多少个,对于小红,从n+1到n+m,hp(x)==fa(n+1)有多少个。

二者取最小值,输出即可。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值