🧠 引言
并查集(Union-Find) 是一种超级实用的数据结构,经常出现在:
- 网络连接判断
- 群组归类
- 图中连通分量计算
虽然它不常出现在日常业务代码中,但在高级算法题、分布式处理、图算法、前端协同编辑系统中,都是核心利器。
本篇我们将用并查集高效解决两个经典问题:
- 朋友圈数量(LeetCode 547)
- 岛屿数量(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() 中用递归压缩路径 |
| 岛屿初始化没加计数 | 每遇到新 1 要 count++ |
| 二维坐标转一维下标错误 | 用 id = i * n + j 计算 |
🧩 拓展任务
- 并查集支持“带权合并”(如最大权值合并)
- 使用路径压缩 + 按秩合并优化性能
- 图的连通性判断、冗余连接检测
📚 总结一句话
并查集是处理“集合归属”和“连通关系”的高效数据结构,用对它,你就能玩转分组合并类问题!
📘 下一篇预告:
第14篇:【用单调栈解决“下一个更大元素”】JS 与 Python 双解搞定股票/气温类问题!