并查集的详解与使用模版(洛谷3367)

并查集的使用情况:

并查集的应用主要是用在多个集合中,取到各个集合之间的关系或者是单个集合之内的关系;

并查集的原理:

1,每个元素的初始父亲都是自己,需要用到的数组也是pre回溯数组,既找到自己的上一个连接元素是谁

2,每个元素对应的父亲的最后一位,就是这个集合的根,根对应的父亲就是他自己

3,多个元素之间,连接的方向其实无所谓,1->3,3->3自己,需要将2加入到集合中,此时3->2和2->3都可以,但总归上,会让各个集合的根相连,2这个集合中只有他本身,所以他就是自己的集合的根,所以2不能与1相连,只能与1,3集合中的根3相连接,至于方向,会使集合中的根不同,一般是让多元素的集合的根连向下一个单独的元素;

并查集的优化:

1,路径压缩存储(time约等于O(1)):

让每个集合中所有元素都在递归中将自己的父亲指向根节点,既所有元素对应的父亲都是该集合的根

注意:

路径压缩主要用于题目有需要的要求时使用,因为会打破原有的结构,但可以大大提高算法的效率;

代码与关建行解释:

#include<bits/stdc++.h>
using namespace std;
const int N=10000;
int pre[N];//用来表示第x位的父亲是谁
int root (int x)
{
	return pre[x]=x==pre[x]?x:root (pre[x]);//将每一位元素的父亲都变为根节点
}
int merge(int x,int y)
{
	x=root(x),y=root(y);
	if(x==y)//说明在同一个集合内
	return 0;
	pre[x]=y;//写pre[y]=x也可以,目的只是将二者的根相连,形成一个集合	
}
void solve()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		pre[i]=i;
	while(m--)
	{
		int op,x,y;
		cin>>op>>x>>y;
		if(op==1)
		{
			merge(x,y);
		}
		else{
			cout<<(root(x)==root(y)?'Y':'N')<<endl;
			
		}
	}
	
}
int main()
{
	solve();
	return 0;
}

其中,root的环节可以采用循环的方式来寻找根节点,因为当数据过大时,递归的方式可能爆栈,所以采用循环的方式就可以避免数据过大导致错误:

int root (int x)
{
	int res = x;
	while(pre[res]!=res)
	res=pre[res];//寻找该集合的根节点res
	while(pre[x]!=x)
	{
		int y=x;
		x=pre[x];//x用来记住自己的父亲,用于下一次遍历
		pre[y]=res;//将该位的父亲更变为根节点(相对于结果而言)
	}
	return res;//当循环到最后的节点的时候,输出他自己(所以这里写x也是一样的结果)
}

 

2,按秩合并:

对于多个集合之间合并为一个大的集合,相当于森林合并成一棵树,为了追求各个集合的元素被根所访问的时间平均最短,一般会让元素少的集合的根连向元素多的集合的根,尽量让整个树变得"又矮又胖";

注意:面对数据量小的题目,与路径压缩的时间复杂度近似,但数据量大差距便会显现;

 

#include<bits/stdc++.h>
using namespace std;
const int N=10000;
int pre[N],rnk[N];//rnk用来存储该集合的秩,既有多少个元素存在
int root (int x)
{
	while(pre[x]^x)//异或符号"^"相当于"!="既二者不相等
	x=pre[x];
	return x;//根节点
}
int merge(int x,int y)
{
	x=root(x),y=root(y);
	if(x==y)//说明在同一个集合内
	return 0;
	if(rnk[x]>rnk[y])
	swap(x,y);//用来保证rnk[x]<==rnk[y]始终成立,便于之后的赋值计算
	pre[x]=y;//将秩小的x(所在的)集合的根连向y
	if(rnk[x]==rnk[y])
	rnk[y]++;//当二者的秩相等时,更新其中一位的数据,默认是y
}
void solve()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		pre[i]=i;
	while(m--)
	{
		int op,x,y;
		cin>>op>>x>>y;
		if(op==1)
		{
			merge(x,y);
		}
		else{
			cout<<(root(x)==root(y)?'Y':'N')<<endl;
			
		}
	}
	
}
int main()
{
	solve();
	return 0;
}

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值