Skip to content

并查集:一种优雅的连通性数据结构

场景引入

社交网络中,如何快速判断两个人是否属于同一个朋友圈?如何快速合并两个朋友圈?并查集就是为这类动态连通性问题而生的数据结构。

核心 API

  • find(x):找到 x 所属集合的代表元素(根节点)
  • union(x, y):合并 x 和 y 所在的集合
  • connected(x, y):判断 x 和 y 是否在同一个集合

并查集操作流程

实现

基础版

javascript
class UnionFind {
  constructor(n) {
    this.parent = Array.from({ length: n }, (_, i) => i);
    this.count = n; // 连通分量数
  }

  find(x) {
    while (this.parent[x] !== x) {
      x = this.parent[x];
    }
    return x;
  }

  union(x, y) {
    const rootX = this.find(x);
    const rootY = this.find(y);
    if (rootX === rootY) return;
    this.parent[rootX] = rootY;
    this.count--;
  }

  connected(x, y) {
    return this.find(x) === this.find(y);
  }
}

优化版(路径压缩 + 按秩合并)

javascript
class UnionFind {
  constructor(n) {
    this.parent = Array.from({ length: n }, (_, i) => i);
    this.rank = new Array(n).fill(0);
    this.count = n;
  }

  find(x) {
    if (this.parent[x] !== x) {
      this.parent[x] = this.find(this.parent[x]); // 路径压缩
    }
    return this.parent[x];
  }

  union(x, y) {
    const rootX = this.find(x);
    const rootY = this.find(y);
    if (rootX === rootY) return false;

    // 按秩合并:矮树接到高树上
    if (this.rank[rootX] < this.rank[rootY]) {
      this.parent[rootX] = rootY;
    } else if (this.rank[rootX] > this.rank[rootY]) {
      this.parent[rootY] = rootX;
    } else {
      this.parent[rootY] = rootX;
      this.rank[rootX]++;
    }
    this.count--;
    return true;
  }

  connected(x, y) {
    return this.find(x) === this.find(y);
  }
}

路径压缩:find 时直接把节点指向根,树高变为 1。 按秩合并:合并时把矮树接到高树下面,防止退化为链表。

两个优化同时使用,find 操作近似 O(1)(严格说是 O(α(n)),反阿克曼函数)。

经典应用

LC 130. 被围绕的区域

把边界上的 O 和内部的 O 用并查集区分——边界 O 连接到一个虚拟节点,最后没连到虚拟节点的 O 就是被围绕的。

LC 990. 等式方程的可满足性

javascript
function equationsPossible(equations) {
  const uf = new UnionFind(26);
  // 先处理 == 方程,合并相等的变量
  for (const eq of equations) {
    if (eq[1] === '=') {
      uf.union(eq.charCodeAt(0) - 97, eq.charCodeAt(3) - 97);
    }
  }
  // 再检查 != 方程,矛盾则返回 false
  for (const eq of equations) {
    if (eq[1] === '!') {
      if (uf.connected(eq.charCodeAt(0) - 97, eq.charCodeAt(3) - 97)) {
        return false;
      }
    }
  }
  return true;
}

复杂度

操作优化后
findO(α(n)) ≈ O(1)
unionO(α(n)) ≈ O(1)
空间O(n)

LeetCode 练习

  • LC 130. 被围绕的区域
  • LC 990. 等式方程的可满足性
  • LC 947. 移除最多的同行或同列石头
  • LC 200. 岛屿数量(并查集解法)
  • LC 684. 冗余连接

面试算法可视化图解