学习笔记4:并查集

在前三篇博客中我讲了最短路问题的三种算法(讲了三篇终于讲完力)这里贴上

学习笔记1:Dijkstra算法(附洛谷P4779题解)_flyfreemrn的博客-CSDN博客

学习笔记2:SPFA(SPFA他死了!)_flyfreemrn的博客-CSDN博客

学习笔记3:floyd算法(多源最短路)_flyfreemrn的博客-CSDN博客

这篇博客主要讲图论中很重要的一个东西:并查集

并查集的主要作用:可以快速合并两个区间和查询一个数是否在某个集合中,均摊下来每次操作的时间复杂度都是O(1)

并查集的主要思:用一个数代表一个集合,可以理解为一棵树,而代表数就是这棵树的根节点

查询时,不断相自己的父节点查询,直到找到根节点,判断两个数是否在同一个集合中,只需要看两个数的根节点是否相同

比如要查询4和6是否在同一个集合中,如下图

 

第一种情况:4和6的根节点都是1,那么4和6就在一个集合中

 

第二种情况:4的根节点是1,6的根节点是5,6和4的根节点不一样,说明4和6不在同一个集合中

除查询外,并查集还能快速的合并两个数所在的集合,只需要将一个数的根节点指向另一个数就行了,如下图

这时有眼尖的读者发现了,啊你这是数据小,如果数据量大了,图还贼歪,就像这样,那岂不是每次操作的时间复杂度都类似O(n),怎么均也均不到O(1)啊

 

说得好(下次不要再说了)这时就要说一下并查集的优化:路径压缩

路径压缩的主要思路就是:每一次查询后,将自己和路径上的所有结点直接指向根节点,就比如我想知道我曾曾曾曾爷爷是谁,第一次要从父亲辈往上查,第二次就直接记住了,不需要再查一次,这样时间复杂度均摊下来就是O(1),如下图

实现起来也十分的容易

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

 没错,只有短短的四行,却饱含这并查集的查询于优化

例题(上一篇学习笔记没贴例题,这次贴个难一点的):[BOI2003]团伙 - 洛谷

其实这道题只需要开一个数组存一个人的敌人,当遇到两个人是敌人的话,就把敌人的敌人放到同一个并查集里,并把没有敌人的人的敌人设成这个敌人

以下AC代码

#include<bits/stdc++.h>
using namespace std;
int n,m,b,c,ans;
char a;
int fr[1005],ttk[1005],use[1005];//朋友,敌人和是否统计过 
int find(int x){//并查集查找 
	if(x==fr[x])return x;
	return fr[x]=find(fr[x]);//优化 
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)fr[i]=i,ttk[i]=-1;//读入 
	for(int i=1;i<=m;i++){
		cin>>a>>b>>c;
		if(a=='F'){//如果是朋友就放到同一个并查集中 
			b=find(b),c=find(c); 
			fr[b]=fr[c];
		}else{
			if(ttk[b]==-1)ttk[b]=c;//如果b没有敌人,把b的敌人设成c 
			else{//否则把c和b的敌人放到一个并查集中 
				int s1=find(ttk[b]),s2=find(c); 
				fr[s1]=fr[s2];
			}
			if(ttk[c]==-1)ttk[c]=b;//同上,把b和c调换一下 
			else{
				int s1=find(ttk[c]),s2=find(b);
				fr[s1]=fr[s2];
			}
		}
	}
	for(int i=1;i<=n;i++){//统计又多少个并查集 
		int s=find(fr[i]);
		if(use[s]==0){
			ans++;
			use[s]=1;
		}
	}
	cout<<ans;
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值