文章目录

大多数情况下,查询只关心根节点是谁,并不关心这棵树的形态,查询的时候,我们只要能判断两个节点的根节点是否相同即可

但是我们希望查询过程中,效率能稍微高一些。希望树能矮一点,这样我们从某个节点出发找根节点时,会更快速

假如当前的并查集如下,我们需要合并7和8所在树的根节点,我们从8出发找到根节点1,我们又从7出发经过相同的路径找到根节点1,然后我们发现根节点相同,不用合并,这样效率就很低了

并查集路径压缩算法改进_算法

优化find

我们进行如下优化:查询的时候将访问过的每个点的父节点修改成树根,这样的方法叫做路径压缩

并查集路径压缩算法改进_并查集_02

由于无论是查询还是合并,我们都是操作根节点,修改后树变矮了,我们查找根节点更快,同时也不影响查询、合并的结果

查询的时候将访问过的每个点的父节点修改成树根,这样的方法叫做路径压缩

并查集路径压缩算法改进_并查集_03

int find_root(int x) {
if (x <= 0 || x >= SIZE) {
return -1;
}

// 用于记录已经遍历过的节点
vector<int> pos;
while (x != parent[x]) {
pos.push_back(x);
x = parent[x];
}
for (int p : pos) {
parent[p] = x;
}
return x;
}

// 递归版本的查询
int cur_find(int x) {
if (x <= 0 || x >= SIZE) {
return -1;
}
if (x == parent[x]) {
return x;
}
else {
// 查询的时候将访问过的每个点的父节点修改成树根
parent[x] = cur_find(parent[x]);
return parent[x];
}
}

find执行以后,后面的查询效率就很好,但美中不足的是,第一次执行find的效率不高,这需要使用加权标记方法优化

加权标记(改进merge)

并查集路径压缩算法改进_数据结构_04

为什么会产生上图那么高的树呢?因为我们merge的时候,父子关系一直都是一个方向

void merge(int x, int y) {
x = cur_find(x);
y = cur_find(y);
if (x != y) {
parent[y] = x; // x永远是y的父亲
// parent[x] = y;
}
}

对于以上的merge代码,我们如果按照如下的方式输入,并执行merge,就会产生图中的树

7   8
6 7
4 6
2 4
1 2
1 3
1 5

我们希望在构建并查集的过程中,进行集合合并的时候,能够使树能矮一点。我们使用加权标记的方式实现,使用一个height数组,记录每个节点的层高,下标则表示节点的编号

merge的时候,谁的height大,谁就作为父节点,由于我们只更新根节点的高度,在矮树挂在高树的情况下,根节点的高度不用更新

如果两棵树一样高,根节点合并以后,需要更新作为父亲的那棵树的根节点的height

并查集路径压缩算法改进_数据结构_05

再像上面一样输入,我们merge的时候,就能得到如下结构的并查集

并查集路径压缩算法改进_算法_06

// 合并x和y所在的集合
void merge(int x, int y) {
x = cur_find(x);
y = cur_find(y);
if (x != y) {
// 这里x和y都是根节点,谁矮谁作为孩子节点
if (height[x] > height[y]) {
parent[y] = x;
}
else {
if (height[x] == height[y]) {
// y作为合并以后树的根,根节点高度要+1
height[y]++;
}
parent[x] = y;
}
}
}

用谁高谁当父亲的方式merge的时候,我们一直用单个节点往树中加入,这棵树一定只有两层

并查集路径压缩算法改进_算法_07


从单个节点开始构造并查集,使用谁高谁当父亲的方式merge,会得到两层的树。我们使用两个两层的树结合,两个根节点一样高,我们随便选取一个节点当父亲,更新该父节点的height即可(并不是更新所有节点的height),因为我们合并的时候只看根节点的高度。所以不是什么时候节点对应的height都是准的,可以保证的是,当前根节点的height是准的

比如我们按照如下的方式构造并查集,就会出现有的节点的height不准确

1   5
1 6
2 3
2 4
1 2

并查集路径压缩算法改进_算法_08

一般来说,在优化的merge中使用优化的find,在find的时候会改变树的结构,但find没有修改height,可能会导致当前根节点的height不准确,使用起来可能会不符合预期。所以我们不把merge加权优化和find优化放在一起使用