并查集-数据结构

并查集

引例:

P 1551 \mathtt{P1551} P1551 加强版( r e l a t i v e \mathtt{relative} relative

题目背景:

若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。

题目描述:

规定: x x x y y y是亲戚, y y y z z z是亲戚,那么 x x x z z z也是亲戚。如果 x x x y y y是亲戚,那么 x x x的亲戚都是 y y y的亲戚, y y y的亲戚也都是 x x x的亲戚。

输入格式:

第一行:三个整数 n , m , p n,m,p n,m,p,( n , m , p ≤ 100000 n,m,p \leq 100000 n,m,p100000),分别表示有 n n n个人, m m m个亲戚关系,询问 p p p对亲戚关系。

以下 m m m行:每行两个数 M i , M j M_i,M_j Mi,Mj 1 ≤ M i , M j ≤ N 1 \leq M_i,M_j \leq N 1Mi,MjN,表示 M i M_i Mi M j M_j Mj具有亲戚关系。

接下来 p p p行,每行两个数 P i , P j P_i,P_j Pi,Pj,询问 P i P_i Pi P j P_j Pj是否具有亲戚关系。

输出格式:

p p p行,每行一个 Y e s \mathtt{Yes} Yes N o \mathtt{No} No。表示第 i i i个询问的答案为“具有”或“不具有”亲戚关系。

这个题目,如果利用简单数组存储信息(即是否为亲戚关系,是的数据相同,不是的数据不同),那么就需要 O ( n 2 ) O(n^2) O(n2)的时间维护这个“数组”,但数据规模达到了 100000 100000 100000,肯定超时。


虽然这样会超时,但是这样的思想却是正确的(即“并查集”)。并查集提供的操作有:

  • 合并两个集合。(并)
  • 查看两个元素是否属于同一个集合。(查)

刚才的解法,正式并查集的一种形式。但这样做会超时。所以我们需要一种更高效的并查集实现形式。这种形式就是树形


那么,我们该如何实现树形的并查集呢?

  • 合并:将某一集合的树根指向另一集合。
  • 查找:一步一步找到自己所在树的根节点。

我们可以用一个 f a t h e r \mathtt{father} father数组来记录自己的父亲,那么合并( m e r g e \mathtt{merge} merge)和查找( f i n d \mathtt{find} find)操作分别如下:

int find(int x)
{
	if(father[x]!=x)return find(father[x]);
	return x;
}
void merge(int x,int y)
{
	int fx=find(x);
	int fy=find(y);
	if(fx!=fy)father[fx]=fy;
}

其中,如果某个节点 x \mathtt{x} x是根节点,那么father[x]=x。因此,并查集初始化( i n i t \mathtt{init} init)时,是这样的:

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

然而,

为什么会 T L E \mathtt{TLE} TLE呢??

因为:如果一颗树是一条链,这样查找时肯定会超时。

解决方法:查找时直接把遇到的节点指向树根(因为我们并不关心这棵树的具体形态)。


因此,我们将 f i n d \mathtt{find} find操作改一改:

int find(int x)
{
	if(father[x]!=x)return father[x]=find(father[x]);
	return x;
}

这种方法时间复杂度为 O ( α ( n ) ) O(\alpha(n)) O(α(n))


A C \mathtt{AC} AC代码如下:

时间复杂度: O ( n + m α ( n ) + q α ( n ) ) O(n+m\alpha(n)+q\alpha(n)) O(n+mα(n)+qα(n))

空间复杂度: O ( n ) O(n) O(n)

#include <stdio.h>
int n,m,q,x,y;
int father[10001];
int find(int x)
{
	if(father[x]!=x)return father[x]=find(father[x]);
	return x;
}
void merge(int x,int y)
{
	int fx=find(x);
	int fy=find(y);
	if(fx!=fy)father[fx]=fy;
}
void init(int x)
{
	for(int i=1;i<=x;i++)
		father[i]=i;
}
int main()
{
	scanf("%d%d%d",&n,&m,&q);
	init(n);
	for(int i=1;i<=m;i++)
	{
	    scanf("%d%d",&x,&y);
	    merge(x,y);
	}
	while(q--)
	{
		scanf("%d%d",&x,&y);
		if(find(x)==find(y))puts("Yes");
		else puts("No");
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值