并查集
并查集是一种树形的结构,重点不在于遍历所有的点而是记录父亲。并查集使用起来就是集合,拥有查找、合并等操作。现在,跟随好渴鹅的脚步,走入并查只因的大门。
并查集初始化
最开始,有 \(n\) 棵树,第 \(i\) 棵树只有自己一个结点。当想要合并时,就把一棵树的根的父亲改为另一棵树,这样子它们就合并成了一个集合。
void init() {
for (int i = 1; i <= n; i++) {
f[i] = i;
}
}
这样子,每个结点的父亲就变成了自己,每个结点都是独立的树。
并查集合并
接下来,我们需要将一棵树的根节点的父亲改为另一棵树:
void U(int u, int v) { // u, v 是根节点
f[u] = v; // 谁是谁的爸爸并不重要,反过来也行
}
但是这里 \(u\) 和 \(v\) 都必须是根节点,而两棵树合并之后可能还会和下一棵树进行合并,因此我们需要实现一个寻找祖宗的函数。
并查集查找
我们可以使用递归一直往当前的爸爸上找,直到 \(f_x=x\),也就是已经是根了我们就停止:
int F(int x) {
return f[x] == x ? x : F(f[x]);
}
但是这样子当 \(n\) 棵树完全合并到了一起时,查找就可能会变成 \(O(n)\),因此我们可以直接存储当前结点的祖宗,这样子就只用遍历一遍,平均速度就达到了约 \(O(\log n)\)。
int F(int x) {
return f[x] == x ? x : f[x] = F(f[x]);
}
接着,我们的合并函数也得加上查找祖宗。
void U(int u, int v) {
u = F(u), v = F(v); // 寻找祖宗
if (u != v) { // 如果不在一棵树里面
f[u] = v;
}
}
模板练习题
给定 \(n\) 个独立的集合和 \(m\) 个询问。对于每一个询问,输入 \(o,x,y\),如果 \(o=1\) 表示合并 \(x\) 和 \(y\),否则判断 \(x\) 和 \(y\) 是否在一个集合内。
伪代码:
void input();
void init();
int F(x); // 这些都是模板
void U(u, v);
int main() {
input(), init();
for (; m; m--) {
cin >> o >> x >> y;
cout << (o == 1 ? (U(x, y), "") : (F(x) == F(y) ? "Yes" : "No")) << '\n';
}
return 0;
}

浙公网安备 33010602011771号