并查集

并查集是一种树形的结构,重点不在于遍历所有的点而是记录父亲。并查集使用起来就是集合,拥有查找、合并等操作。现在,跟随好渴鹅的脚步,走入并查只因的大门。

并查集初始化

最开始,有 \(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;
}
posted @ 2023-10-15 15:12  haokee  阅读(14)  评论(0)    收藏  举报