在前三篇博客中我讲了最短路问题的三种算法(讲了三篇终于讲完力)这里贴上
学习笔记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;
}