并查集
1.简介
并查集(Union-Find),是一种用于处理动态连通性问题的数据结构。最早由 Bernard A. Galler 和 Michael J. Fischer 在1964年提出。它可以高效地管理一些不相交的集合,支持合并两个集合(union)和查找某个元素所属集合(find)这两种操作。然而,并查集的概念在计算机科学领域中的应用可以追溯到更早的年代。这个数据结构主要用于处理集合的不相交问题,比如在图论算法中的最小生成树算法(如Kruskal和Prim算法)以及在图的连通性问题中经常会用到并查集。
并查集最开始被广泛应用于算法和数据结构领域,特别是在解决图论中的连通性和最小生成树等问题时。随着计算机科学的发展,特别是在并行计算、网络连接和数据库系统等领域,并查集的应用也逐渐增多。今天,并查集已经成为解决许多实际问题的重要工具,包括社交网络分析、图像处理、游戏开发、网络通信等领域。在实际工程和科研中,我们可以经常看到并查集的身影。
并查集的两个操作
- Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
- Union:将两个子集合并成同一个集合
并查集的实现通常使用两种优化技巧:
- 路径压缩(Path Compression): 优化
find
操作,使得树的深度尽量变小,以加速后续的查询。 - 按秩合并(Union by Rank)或按大小合并(Union by Size): 优化
union
操作,确保合并时较小的树挂在较大的树上,从而避免树变得过高。
2.基础算法
在并查集中,每个集合由一棵树来表示,其中树的每个节点指向其父节点,直到根节点。根节点表示集合的代表元素。
初始化时,每个元素各自构成一个单元素集合,每个元素的父节点指向自己,即构成一棵只有根节点的树。
查找操作通过递归或迭代地沿着父节点指针向上查找,直到找到根节点,从而确定元素所在的集合。
合并操作将两个集合合并为一个集合,通常是将其中一个集合的根节点的父节点指向另一个集合的根节点,从而实现两个集合的合并。
#include <bits/stdc++.h>
using namespace std;
const int N=1e6;
int fa[N],h[N];//第一个存储父亲节点,第二个存储树的高度
int getroot(int x){
if (fa[x]==x){//如果我的父亲是我自己
return x;//那么我就是祖先
}else {
return getroot(fa[x]);//否则就用递归的方式去找祖先
}
}
void merge (int x,int y){//为x向y合并
int p=getroot(x),q=getroot(y);//先获取两个要融合的集合的祖先
fa[p]=q;//将x的祖先的父亲设为y的祖先
return;
}
int main() {
for (int i=1;i<=1000;i++){//初始化
fa[i]=i;//每个人的祖先都应该是自己
h[i]=1;//高度为一
}
return 0;
}
3.路径压缩
在进行递归的时候存储一下祖先。
#include <bits/stdc++.h>
using namespace std;
const int N=1e6;
int fa[N],h[N];//第一个存储父亲节点,第二个存储树的高度
int getroot(int x){
if (fa[x]==x){//如果我的父亲是我自己
return x;//那么我就是祖先
}else {
fa[x]=getroot(fa[x]);
return fa[x];//否则就用递归的方式去找祖先
}
}
void merge (int x,int y){//为x向y合并
int p=getroot(x),q=getroot(y);//先获取两个要融合的集合的祖先
fa[p]=q;//将x的祖先的父亲设为y的祖先
return;
}
int main() {
for (int i=1;i<=1000;i++){//初始化
fa[i]=i;//每个人的祖先都应该是自己
h[i]=1;//高度为一
}
return 0;
}
4.按秩合并
4.1按深度合并
因为我们不知道那棵树的高度更高,所以我们可以对树的高度进行一个维护
//按深度合并
void Union(int x, int y)
{
int fx = fa[x], fy = fa[y];
if (fx == fy)
return;
if (dep[fx] > dep[fy])
swap(fx, fy);
fa[fx] = fy;
if (dep[fx] == dep[fy])//注意:只有两个深度相同时,合并总深度才会增大
dep[fy]++;
}
4.2按长度合并
和上面差不多,就不放代码了
5.例子
假设有5个元素的并查集,初始时各元素自成一个集合:
parent: [0, 1, 2, 3, 4]
进行如下操作:
-
union(0, 1)
合并 0 和 1:parent: [0, 0, 2, 3, 4]
-
union(1, 2)
合并 1 和 2:parent: [0, 0, 0, 3, 4]
-
find(2)
查找 2 的根:结果为 0
此时所有连通的节点都是以0
为根,find(2)
的结果为0
,路径压缩将所有访问路径上的节点直接连接到根节点0
总结
并查集是一种简单但非常高效的数据结构,特别适用于动态连通性问题。通过路径压缩和按秩合并这两种优化技巧,并查集能够以接近常数时间处理合并和查找操作,这在图论算法中具有广泛的应用。