一、并查集是什么?
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。
并查集算法(union-find algorithm)定义了两个用于此数据结构的操作:
- Find:确定元素属于哪一个子集。这个确定方法就是不断向上查找找到它的根节点,它可以被用来确定两个元素是否属于同一子集。
- Union:将两个子集合并成同一个集合。
- MakeSet:用于建立单元素集合。
为了更加精确的定义这些方法,需要定义如何表示集合。一种常用的策略是为每个集合选定一个固定的元素,称为代表,以表示整个集合,即树的根节点。接着,Find(x) 返回 x 所属集合的代表,而 Union 使用两个集合的代表作为参数,将两个树进行合并。
二、伪码实现
并查集(树)是一种将一个集合以树形结构进行组合的数据结构,如下图所示。其中每一个节点保存着到它的父节点的引用。
伪代码实现如下:
function MakeSet(x)
x.parent := x
function Find(x)
if x.parent == x
return x
else
return Find(x.parent)
function Union(x, y)
xRoot := Find(x)
yRoot := Find(y)
xRoot.parent := yRoot
通过这种方法创建的树可能会严重不平衡,可以用下面方法优化。
1.按秩合并
总是将更小的树连接至更大的树上。因为影响运行时间的是树的深度,更小的树添加到更深的树的根上将不会增加秩除非它们的秩相同。在这个算法中,术语“秩”替代了“深度”,因为同时应用了路径压缩时(见下文)秩将不会与高度相同。单元素的树的秩定义为0,当两棵秩同为r的树联合时,它们的秩r+1。如下图所示:
伪码实现如下:
function MakeSet(x)
x.parent := x
x.rank := 0
function Union(x, y)
xRoot := Find(x)
yRoot := Find(y)
if xRoot == yRoot
return
// x和y不在同一个集合,合并它们。
if xRoot.rank < yRoot.rank
xRoot.parent := yRoot
else if xRoot.rank > yRoot.rank
yRoot.parent := xRoot
else
yRoot.parent := xRoot
xRoot.rank := xRoot.rank + 1
2. 路径压缩
在执行“查找”时扁平化树结构的方法。关键在于在路径上的每个节点都可以直接连接到根上;他们都有同样的表示方法。为了达到这样的效果,Find递归地经过树,改变每一个节点的引用到根节点。得到的树将更加扁平,为以后直接或者间接引用节点的操作加速。
伪码实现如下:
function Find(x)
if x.parent != x
x.parent := Find(x.parent)
return x.parent
三、C++代码实现
class UnionFind{
private:
vector<int> parent;
vector<int> rank;
public:
//构造函数
UnionFind(int n){
parent=vector<int>(n);
for(int i=0;i<n;++i){
parent[i]=i; //每个节点的父节点初始化为自己
}
rank=vector<int>(n,0);
}
int find(int i){ //路径压缩
if(parent[i]!=i){
parent[i]=find(parent[i]); //在find的过程中递归实现
}
return parent[i];
}
void union(int i, int j){ //按秩合并
int rooti=find(i);
int rootj=find(j);
if (rooti==rootj) return;
// 让rooti节点为最后的根节点
if (rank[rooti]<rank[rootj]){
swap(rooti,rootj);
}
if(rank[rooti]==rank[rootj]){
rank[rooti]++;
}
parent[rootj]=rooti;
}
}