并查集学习

并查集学习

并查集用于解决元素分组的问题,管理一系列不相交的集合,并支持两种操作:

  • 合并:把两个不相交的集合合并一个集合
  • 查询:查询两个元素是否在同一个集合中
    参考:
    详细分析

代码模板:

最简单的版本(易理解但效率较低)

初始化:

int fa[MAXN];

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

查询:
一层一层访问父节点,直至根节点(根节点的标志就是父节点是本身)。要判断两个元素是否属于同一个集合,只需要看它们的根节点是否相同即可。

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

合并:
合并操作先找到两个集合的根元素,然后将前者的父节点设为后者,也可以将后者的父节点设为前者。

void merge(int i,int j)
{
	fa[find(i)] = find(j);
}

存在问题:最简单的并查集效率是比较低的。

路径压缩版(优化)

路径压缩解决效率问题。查询的过程中,把沿途的每个节点的父节点都设为根节点。

int find(int x)
{
	if(x==fa[x]) return x;
	else
	{
		fa[x] = find(fa(x));//将父节点设为根节点
		return fa[x]; //返回父节点
	}
}

进一步优化——按秩合并

路径压缩和按秩合并如果一起使用,时间复杂度接近 O(n);
思路:
用一个数组rank[]记录每个根节点对应的树的深度(如果不是根节点,其rank相当于以它作为根节点的子树的深度)。
最开始,把所有元素的rank(秩)设为1。
合并时比较两个根节点,把rank较小者往较大者上合并

初始化(按秩合并)

void init(int n)
{
	for(int i=0;i<n;i++)
	{
		fa[i]=i;
		rank[i]=1;
	}
}

合并(按秩合并)

void merge(int i,int j)
{
	int x = find(i),y = find(j);
	if(rank[x] <= rank[y]) fa[x] = y;
	else fa[y] = x;
	if(rank[x] == rank[y] && x!=y) rank[y] ++;//深度相同,新的根节点需要+1
}

并查集的题目练习

亲戚问题

题目背景
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
题目描述
规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。
输入格式
第一行:三个整数n,m,p,(n<=5000,m<=5000,p<=5000),分别表示有n个人,m个亲戚关系,询问p对亲戚关系。
以下m行:每行两个数Mi,Mj,1<=Mi,Mj<=N,表示Mi和Mj具有亲戚关系。
接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。
输出格式
P行,每行一个’Yes’或’No’。表示第i个询问的答案为“具有”或“不具有”亲戚关系。

代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>

using namespace std;

const int N=1000;
int fa[N],r[N];

//初始化 
void init(int n)
{
	for(int i=1;i<=n;i++)
	{
		fa[i]=i;
		r[i]=1;
	}
} 

//查找 
int find(int x)
{
	if(x==fa[x]) return x;
	else 
	{
		fa[x] = find(fa[x]);
		return fa[x];	
	}	
 } 

//合并 (按秩) 
void merge(int i,int j)
{
	int x = find(i),y = find(j);
	if(r[x] <= r[y]) fa[x] = y;
	else fa[y] = x;
	if(r[x]==r[y] && x!=y) r[y]++;
}

int main()
{
	int n,m,p;
	cin>>n>>m>>p;
	init(n);//初始化 
	int i,j;
	while(m--)
	{
		cin>>i>>j;
		merge(i,j);//合并亲戚关系 
	}
	int pi,pj;
	while(p--)
	{
		cin>>pi>>pj;
		if(find(pi) == find(pj))
		{
			cout<<"YES"<<endl;
		}
		else cout<<"NO"<<endl;
	}
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值