【算法】并查集

本文介绍了并查集数据结构在亲戚问题中的应用,包括主要思想(通过find函数和路径压缩判断亲戚关系),提供了一个基础和进阶的题目实例,并给出了相应的C++代码。
摘要由CSDN通过智能技术生成

并查集简介

并查集是一种常见的数据结构
主要用于解决"亲戚"问题,如 x x x 的亲戚为 y y y y y y 的亲戚为 z z z,那么我们说 x x x 也是 z z z 的亲戚

主要思想

我们定义一个数组fa[x]表示 x x x父亲节点
我们定义一种函数find(x)查询 x x x 所属路径的根节点

有什么用呢?
x x x y y y 形成亲戚关系是,我们令 y y y x x x祖先(即 find(x) )的父亲
最终会有亲戚关系的会形成如下的一条链在这里插入图片描述
所以 x x x y y y z z z f f f 互为亲戚
此时, f i n d ( x ) find(x) find(x) f f f
那么我们可以根据 find() 函数是否相同判断两个人是否为亲戚
还有一个东西叫路径压缩
如找 f i n d ( x ) find(x) find(x) 时,可以把 x x x 的父亲直接链接到祖先
如图,红线指向压缩后的父亲
在这里插入图片描述
路径压缩后时间复杂度将会减少,不会一次一次慢慢找了

模板代码

题目链接P1551 亲戚

#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;
int n,m,p,x,y,f[5005];
int find(int x){ //找x的祖先
	if(f[x]==x) return x; 
	else return f[x]=find(f[x]); // 路径压缩
}
int main(){
	scanf("%d%d%d",&n,&m,&p);
	for(int i=1;i<=n;i++) f[i]=i; //初始化,i的父亲为i
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		f[find(x)]=find(y); //x的祖先的父亲链接到y的祖先
	}
	for(int i=1;i<=p;i++)
	{
		scanf("%d%d",&x,&y);
		if(find(x)==find(y)) cout<<"Yes"<<endl; //如果祖先相同,那么肯定就是亲戚
		else cout<<"No"<<endl;
	}
	return 0;
}

进阶题目

题目链接P1892 [BOI2003] 团伙
相对于基础题目,这道题多了一个敌人关系
一个人不可以和自己的敌人组成一个团伙
但是

一个人的朋友的朋友是朋友
一个人的敌人的敌人是朋友

如下图, 2 2 2 的敌人为 1 1 1 4 4 4 的敌人为 1 1 1,所以 2 2 2 4 4 4 为朋友
在这里插入图片描述

样例

样例输入

6
4
E 1 4
F 3 5
F 4 6
E 1 2

样例输出

3

样例解释

在这里插入图片描述

解决思路

我们可以发现,如果只有朋友关系的图
那么敌人关系无关系容易弄混
所以我们开两倍n的数组,大于 x + n x+n x+n 的部分表示 x x x 的敌人
然后当 x x x y y y 形成敌人关系时,我们把 x + n x+n x+n y y y 相连

f [ f i n d ( x ) ] = f i n d ( y + n ) ; f[ find(x)]=find(y+n); f[find(x)]=find(y+n);
f [ f i n d ( x + n ) ] = f i n d ( y ) ; f[find(x+n)]=find(y); f[find(x+n)]=find(y);

代码

#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;
int n,m,x,y,f[2005],a[2005],ans;
char c;
int find(int x){ //并查集模板(找x的祖先)
	if(f[x]==x) return x;
	else return f[x]=find(f[x]); //路径压缩
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=2*n;i++) f[i]=i; //i的父亲暂时为i
	for(int i=1;i<=m;i++)
	{
		cin>>c; scanf("%d%d",&x,&y);
		if(c=='F') //如果x于y为朋友关系
			f[find(x)]=find(y); //同模板代码
		else 
		{
			f[find(x)]=find(y+n); //x的祖先与y的敌人相连
			f[find(x+n)]=find(y); //x的敌人的祖先于y的祖先相连
		}
	}
	for(int i=1;i<=n;i++)
	{
		if(a[find(i)]==0) ans++; //如果i的祖先所属的链没有出现,那么结果+1
		a[find(i)]=1; //i的祖先所属的链出现了,记录
	}
	printf("%d",ans);
	return 0;
}
  • 35
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
克鲁斯卡尔算法是一种用于求解最小生成树的贪心算法,而并查集是一种用于维护元素分组信息的数据结构。它们在解决图论问题中经常一起使用。 克鲁斯卡尔算法的基本思想是,通过不断选择边权值最小且不会产生环路的边,逐步构建最小生成树。在实现过程中,使用并查集来判断两个节点是否属于同一个连通分量,以避免形成环路。 并查集是一种用于解决集合合并与查询问题的数据结构。它通过维护一棵树来表示每个元素所属的集合,其中每个节点指向其父节点,树的根节点表示该集合的代表元素。通过路径压缩和按秩合并等优化策略,可以提高并查集的效率。 在克鲁斯卡尔算法中,首先将图中的所有边按权值从小到大排序,然后依次选择边进行判断。当选择一条边时,判断该边连接的两个节点是否属于同一个连通分量。如果不属于同一个连通分量,则选择该边,并将两个节点合并到同一个连通分量中。重复这个过程直到选择了 n-1 条边,其中 n 是图中节点的个数,即得到最小生成树。 克鲁斯卡尔算法的时间复杂度主要取决于排序边的时间复杂度,一般情况下为 O(ElogE),其中 E 是边的数量。并查集的操作时间复杂度为 O(α(n)),其中 α(n) 是一个非常慢增长的函数,可以认为是常数级别。因此,整个算法的时间复杂度为 O(ElogE)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值