【前端也要懂并查集?】JS 与 Python 解“朋友圈”和“岛屿数量”!

55 阅读3分钟

🧠 引言

并查集(Union-Find) 是一种超级实用的数据结构,经常出现在:

  • 网络连接判断
  • 群组归类
  • 图中连通分量计算

虽然它不常出现在日常业务代码中,但在高级算法题、分布式处理、图算法、前端协同编辑系统中,都是核心利器。

本篇我们将用并查集高效解决两个经典问题:

  1. 朋友圈数量(LeetCode 547)
  2. 岛屿数量(LeetCode 200)

并使用 JavaScript 与 Python 双解完整演示!


🧱 一、什么是并查集?

并查集是一种支持**“快速合并”和“快速查询”**的树形结构。

核心操作:

  • find(x):查找 x 的根(代表元素)
  • union(x, y):合并 x 和 y 所属的集合
  • isConnected(x, y):判断 x 和 y 是否属于同一集合

🧪 Part 1:朋友圈数量(LeetCode 547)

❓题目描述

n 个人,M[i][j]=1 表示第 i 和第 j 人是朋友(对称矩阵)。找出朋友圈个数(即连通块数量)。

输入: [[1,1,0],[1,1,0],[0,0,1]]
输出: 2

✅ 解法思路

  • 初始化每人自成一个集合
  • 如果两人互为朋友,就合并他们的集合
  • 最后统计不同根节点数量即为朋友圈数

💻 JavaScript 实现

class UnionFind {
  constructor(n) {
    this.parent = Array.from({ length: n }, (_, i) => i);
  }

  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) {
      this.parent[rootX] = rootY;
    }
  }

  count() {
    const roots = new Set(this.parent.map(x => this.find(x)));
    return roots.size;
  }
}

function findCircleNum(M) {
  const n = M.length;
  const uf = new UnionFind(n);

  for (let i = 0; i < n; i++) {
    for (let j = i + 1; j < n; j++) {
      if (M[i][j] === 1) uf.union(i, j);
    }
  }

  return uf.count();
}

🐍 Python 实现

class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))

    def find(self, x):
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])  # 路径压缩
        return self.parent[x]

    def union(self, x, y):
        self.parent[self.find(x)] = self.find(y)

    def count(self):
        return len(set(self.find(x) for x in self.parent))

def find_circle_num(M):
    n = len(M)
    uf = UnionFind(n)

    for i in range(n):
        for j in range(i + 1, n):
            if M[i][j] == 1:
                uf.union(i, j)

    return uf.count()

🧪 Part 2:岛屿数量(LeetCode 200)

❓题目描述

给一个二维 grid,由 '1' 表示陆地,'0' 表示水,计算岛屿个数(上下左右连接的 1 为一块陆地)。

输入: grid =
[
 ["1","1","0","0"],
 ["1","0","0","1"],
 ["0","0","1","1"]
]
输出: 3

✅ 解法思路(并查集版)

  • 把二维地图压成一维下标
  • 每个 1 是一个节点
  • 相邻的 1 合并为一个集合
  • 最后统计根节点数

💻 JavaScript 实现

function numIslands(grid) {
  const m = grid.length, n = grid[0].length;
  const uf = new UnionFind(m * n);

  let count = 0;
  const dirs = [[0,1],[1,0]];

  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (grid[i][j] === '1') {
        count++;
        for (let [dx, dy] of dirs) {
          const ni = i + dx, nj = j + dy;
          if (ni < m && nj < n && grid[ni][nj] === '1') {
            const id1 = i * n + j;
            const id2 = ni * n + nj;
            if (uf.find(id1) !== uf.find(id2)) {
              uf.union(id1, id2);
              count--;
            }
          }
        }
      }
    }
  }

  return count;
}

🐍 Python 实现

class UnionFind:
    def __init__(self, size):
        self.parent = list(range(size))

    def find(self, x):
        if x != self.parent[x]:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]

    def union(self, x, y):
        self.parent[self.find(x)] = self.find(y)

def num_islands(grid):
    if not grid: return 0
    m, n = len(grid), len(grid[0])
    uf = UnionFind(m * n)
    count = 0

    for i in range(m):
        for j in range(n):
            if grid[i][j] == '1':
                count += 1
                for dx, dy in [(1, 0), (0, 1)]:
                    ni, nj = i + dx, j + dy
                    if 0 <= ni < m and 0 <= nj < n and grid[ni][nj] == '1':
                        id1, id2 = i * n + j, ni * n + nj
                        if uf.find(id1) != uf.find(id2):
                            uf.union(id1, id2)
                            count -= 1
    return count

⚠️ 易错点总结

问题正确做法
没写路径压缩find() 中用递归压缩路径
岛屿初始化没加计数每遇到新 1count++
二维坐标转一维下标错误id = i * n + j 计算

🧩 拓展任务

  • 并查集支持“带权合并”(如最大权值合并)
  • 使用路径压缩 + 按秩合并优化性能
  • 图的连通性判断、冗余连接检测

📚 总结一句话

并查集是处理“集合归属”和“连通关系”的高效数据结构,用对它,你就能玩转分组合并类问题!


📘 下一篇预告:

第14篇:【用单调栈解决“下一个更大元素”】JS 与 Python 双解搞定股票/气温类问题!