基础算法学习:并查集
并查集(Union-Find) 是一种用于处理集合合并和查询的高效数据结构。它支持两种操作:
- 合并(Union) :将两个集合合并成一个集合。
- 查找(Find) :查找某个元素所在的集合(即查找元素的代表)。
并查集通常用于解决一些动态连通性问题,例如图的连通性问题、网络连接问题等。
并查集的基本思想:
并查集维护了一个集合(或树)的集合。每个集合由一个元素的代表(通常是树的根节点)表示。每个元素指向其父元素(指向树中的父节点),根节点指向自己。通过路径压缩和按秩合并技术,能够使并查集操作更加高效。
主要操作:
-
Find:查找某个元素所在的集合(即查找该元素的代表元素)。这通常是通过递归或者迭代查找元素的父节点实现的。
- 路径压缩(Path Compression) :在查找时,通过将访问路径上所有节点直接指向根节点,从而加速未来的查询。
-
Union:将两个集合合并成一个集合。通常,我们会将一个集合的根节点指向另一个集合的根节点。
- 按秩合并(Union by Rank/Size) :为了保持树的平衡,通常会将较小的树合并到较大的树上,避免树的深度过大,从而提高查找效率。
并查集的时间复杂度:
通过路径压缩和按秩合并,虽然并查集的操作理论上是树形结构,但树的高度保持非常低,实际时间复杂度接近于 O(α(n)) ,其中 α(n) 是 阿克曼函数的反函数,其增长极其缓慢,因此在实际中可以认为是常数时间。
并查集板子
(1)朴素并查集:
int p[N]; //存储每个点的祖宗节点
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
(2)维护size的并查集:
int p[N], size[N];
//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
size[i] = 1;
}
// 合并a和b所在的两个集合:
size[find(b)] += size[find(a)];
p[find(a)] = find(b);
(3)维护到祖宗节点距离的并查集:
int p[N], d[N];
//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x)
{
int u = find(p[x]);
d[x] += d[p[x]];
p[x] = u;
}
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
d[i] = 0;
}
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量