并查集(Disjoint Set)
解决的问题
- 查找图是否成环
- 查询数据是否在同一个连通图中
思想
利用数组建树,数组元素值代表该位置的父亲结点,如果为数组元素值为本身代表为独立结点
找祖先
每次询问自己的父亲,直到查找到数组元素值为本身的点即为祖先。
合并两个圈
合并两圈=把a2图的头结点的父亲结点改为a1图的头结点
成环的标志是:圈内某两元素之间还有一条边
也就是: 当发现某条边的两个结点的根节点是同一结点时,代表着成环了
压缩路径
可能会出先这种情况,复杂度变为O(n)
因此需要优化:压缩路径
直接把在路径上的每个节点都直接连接到根上
模板代码
模板题目
#include
using namespace std;
const int N=1e5+10;
int fa[N];//father
void init(int n){
for(int i=1;i<=n;i++){
fa[i]=i;
}
}
int getfa(int x){
if(fa[x]!=x) fa[x]=getfa(fa[x]);
return fa[x];
}
void join(int x,int y){
x=getfa(x);
y=getfa(y);
if(x!=y)fa[x]=y;
}
int main()
{
int n,m;
cin>>n>>m;
init(n);
while(m--)
{
int x,y,z;
cin>>x>>y>>z;
if(x==1)
{
join(y,z);
}
else
{
if(getfa(y)==getfa(z)) cout<<"Y"<
代码优化
在合并集合时,无论将哪一个集合连接到另一个集合的下面,都能得到正确的结果。但不同的连接方法存在时间复杂度的差异。
所以合并时利用点数和深度的估价函数来降低时间复杂度。
//记录并初始化子树的大小为 1
void Join(int x, int y)
{
x=find(x), y=find(y);
if (x==y) return;
if (size[x] > size[y]) // 保证小的合到大的里
swap(x, y);
fa[x] = y;
size[y] += size[x];
}//按大小合并
//记录并初始化子树的深度为 1
int depth[maxn];// 深度
void Join(int x, int y)
{
x=find(x),y=find(y);
if (x==y) return;
if(depth[x]depth[y])fa[y]=x;
if(depth[x]==depth[y])
{
depth[y]++;
fa[x]=y;
}// 深度小的合并到深度大的集合里
/*
因为将压缩了路径,也就是说直接将数据连接到根节点;
也就是说如果两个高度不一样的并查集合并,深度不会变化,就是最深的树的深度。
*/
}