并查集++洛谷P1551 亲戚题解

本文详细介绍了并查集这一数据结构,包括它的定义、作用和典型应用场景。通过一个亲戚关系查询的例题,阐述了并查集如何解决集合合并与查询的问题,以及如何通过路径压缩和按秩合并优化算法,提高查询效率。并提供了具体的C++代码实现,帮助读者理解并查集的实际运用。
摘要由CSDN通过智能技术生成

今天没有写博客,就想着把之前一直想写却没来得及写的并查集写一下,对于并查集,算我掌握比较好的算法

What is 并查集?

关于并查集的定义,百度百科的解释为

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

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

 Why 并查集?

在输入时,每一个点都是一个只有一个单元素的集合,并查集的作用就是把相连的边并与一个集合中,成为一棵树

具体操作就是对于点i与点j相连的边有father[i]=j,既将边的一个节点设为另一个节点的父节点,这样就把这两个节点连接起来,放入在一个集合中。

例题详解

以上所有所说的都是空泛而不实用的,具体的内容靠一道例题来讲解

亲戚

问题描述

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

输入

第一行:三个整数 n,m,p (n<=5000,m<=5000,p<=5000)

分别表示有n个人,m个亲戚关系,询问p对亲戚关系

以下m行:每行两个数M[i],M[j],1<=M[i],M[j]<=n,表示M[i],M[j]具有亲戚关系。

接下来的p行:每行两个数P[i],P[j],询问P[i]和P[j]是否具有亲戚关系;

输出

共p行 每行一个“Yes“或”No“。表示第P[i]个询问的答案为”有“或者”没有“亲戚关系。

输出样例:                                    输出样例:

6 5 3                                               Yes

1 2                                                  Yes

1 5                                                   No

3 4

5 2

1 3

1 4

2 3

5 6
 

这道题可以被看做并查集的模板,从这道题中我们就很清楚明白,将有亲戚关系的点放入一个集合中,然后查看这两个点是否在同一个集合中,即是否具有亲戚关系;

那么实现将两个点放入同一个集合中的操作就是建树,然后看这两个节点是否在这个树中;

具体操作 

先将数组初始化

for(int i=1;i<=n;i++) father[i]=i;

将每一个节点的父节点设为他自己

for(int i=1;i<=m;i++){
	father[m[i]]=m[j];                                   
}

之后,每一个点的父节点都是另一个点,之后就建立了一棵树

int find_pre(int i){
	if(father[i]!=i) return find_pre(father[i]);
	//当father[i]不是根节点,就在找该点的父节点 
}

追后,通过该函数找到根节点,如果,根节点相同,就说明是亲戚

问题&&改进

到这里,可能就会认为,并查集就是这?就结束了??

当然没有!

试想,在上述样例中,有点(1,3),点(1,4)

如果实行上述代码,就会出现一个问题father[1]=3,father[1]=4;

下一个点直接将上一个点覆盖掉,如此下来,最后集合必然少一个点

而且在每一次都要遍历一遍询问点以上的每一个点,浪费时间

遇到问题就要解决问题

那么解决方法就很简单

每一个点都直接接在根节点上,那么这棵树深度就为2;

具体方法

int find_pre(int x){
	if(father[x]!=x) return father[x]=find_pre(father[x]);
	//找根结点的时候,顺便将该点连在根节点上;
	else return x; 
} 
void Union(int x,int y){
	int fa=find_pre(x);
	int fb=find_pre(y);
	if(fa!=fb) faturn[fa]=fb;//如果两个点不再同一棵树上,就把这两点的根节点相连 
}

 结合这道题

思路很明白,就是像上面一样

我就直接上代码了

#include<bits/stdc++.h>
#define M 200005 
using namespace std;
int a[M];
int find_pre(int x){
	if(a[x]!=x) return a[x]=find_pre(a[x]);
	else return x;
}
void ans(int c,int d){
	if(find_pre(a[c])==find_pre(a[d]))
	cout<<"Yes"<<endl;
	else cout<<"No"<<endl;
}
void Union(int e,int f){
	int ee=find_pre(a[e]);
	int dd=find_pre(a[f]);
	if(ee!=dd) a[dd]=ee;
}
int n,m,k;
int main (){
	ios::sync_with_stdio(false);
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++){
		a[i]=i;//初始化 
	}
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		Union(x,y);//加入边 
	}
	for(int i=1;i<=k;i++){
		int q,p;
		cin>>q>>p;
		ans(q,p);//答案 
	}
	return 0;
}

直接AC了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值