数据结构合集

发布时间 2023-07-13 10:11:46作者: luqyou

并查集

普通并查集

先看一个问题:

P1551 亲戚

genjoedoam
规定:\(x\)\(y\) 是亲戚,\(y\)\(z\) 是亲戚,那么 \(x\)\(z\) 也是亲戚。如果 \(x\)\(y\) 是亲戚,那么 \(x\) 的亲戚都是 \(y\) 的亲戚,\(y\) 的亲戚也都是 \(x\) 的亲戚。现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。

我们令 \(f_i\) 为节点 \(i\) 的父亲。一开始,\(f_i=i\),因为最开始 \(i\) 的父亲不是任何人。

接下来开始维护“合并”操作。对于一条“\(x\)\(y\) 的亲戚”的一条信息,不妨设 \(x\)\(y\) 的父亲,此时我们爬树找到 \(y\) 的“祖先”(可以发现,并查集建完之后是一个森林,就相当于找到节点 \(y\) 所在的树的根节点),记为 \(fy\),将 \(f_{fy}\) 设为 \(x\) 所在树的根节点即可。

int find(int x){//寻找 x 所在树的根节点
	if(f[x]==x){
		return f[x];
	}
	else{
		return find(f[x]);
	}
}
int merge(int x,int y){//将 y 所在的集合(树)合并到 x 所在的集合(树)
	int fx=find(x),fy=find(y);
	f[fy]=fx;
}

那么“查询” \(x\)\(y\) 是否在同一个集合内。我们不断从节点 \(x\) 和节点 \(y\) 向上爬树,如果他们的祖先相同,那么必定在一棵树内,即具有亲戚关系,否则就不具有亲戚关系。

bool query(int x,int y){//查询 x 与 y 是否在一个集合
	if(find(x)==find(y)){
		return 1;
	}
	return 1;
}

并查集的路径压缩优化

为什么要路径压缩?

考虑并查集的这种情况:

这样的话,每次查询操作的时间复杂度就会退化为线性。

那么怎么进行路径压缩?

我们每次查询的时候直接把查询一路上的所有点的 \(f\) 值直接设为最终查询的结果即可。

我们对上图进行路径压缩:

代码如下:

int find(int x){
	if(f[x]!=x){
		f[x]=find(f[x]);//路径压缩,即将每个访问路径上的点的父亲都直接设为这棵树的根节点
	}
	return f[x];
}

优化了整整 \(100ms\)