LeetCode 323. 无向图中连通分量的数目
题目描述
题意分析
图中有
n个节点,编号为0到n - 1,每条边表示两个端点可以互相到达。一个连通分量由所有彼此可达的节点组成,组外节点与这组均不连通,要求统计这样的组有多少个。没有出现在任何边中的孤立点也各自构成一个分量。
解法:并查集计数连通块
核心思路
[!blue]
可以把边逐条加入图中。没有处理任何边时,所有节点互不相连,共有
n个分量;每加入一条边,只可能把它两端所在的两个分量连接起来,不会影响其他分量。因此用并查集维护当前分组,同时维护计数components。
parent把每个集合组织成一棵以代表节点为根的树,根满足parent[root] == root。find(node)沿父指针找到这个根,同根就表示已经连通。不能只比较端点的直接父节点,因为同一集合中的不同节点可能还经过不同的中间父节点。处理边
(u, v)时,先得到rootA、rootB:若相同,这条边只是在已有连通分量内部增加连接,计数不变;若不同,连接两个根,原来的两个分量变成一个,恰好令components--。所以无论图中有多少环,都只对真正合并的那一次减一。为了让父指针树尽量浅,
size[root]保存根所在集合的节点数,始终把小集合的根挂到大集合的根下,并增加大集合的大小。查找时还采用路径折半:让当前节点直接指向祖父节点,再沿新父节点继续。祖父和原父属于同一集合,所以根和连通关系不变,但以后查找需要经过的层数更少。初始化使并查集与无边图的连通分量完全一致;每条边只在两个分量原本不同的情况下合并它们,继续保持这一对应关系。所有边处理完时,
components就是原图的连通分量数。孤立点从未被合并,因此已经包含在答案中。
解题步骤
- 初始化每个节点为自己的根,集合大小为一,分量数为 n。
- 逐条边查找两个根。
- 同根跳过,不同根按大小合并并将数量减一。
- 返回最终分量数。
代码实现
class Solution {
public int countComponents(int n, int[][] edges) {
int[] parent = new int[n];
int[] size = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
size[i] = 1;
}
int components = n;
for (int[] edge : edges) {
int rootA = find(parent, edge[0]);
int rootB = find(parent, edge[1]);
// 同一分量内的边不减少分量数量。
if (rootA == rootB) {
continue;
}
if (size[rootA] < size[rootB]) {
int temp = rootA;
rootA = rootB;
rootB = temp;
}
// 小集合挂到大集合,只有这次成功合并才减少一个分量。
parent[rootB] = rootA;
size[rootA] += size[rootB];
components--;
}
return components;
}
private int find(int[] parent, int node) {
while (node != parent[node]) {
parent[node] = parent[parent[node]];
node = parent[node];
}
return node;
}
}
func countComponents(n int, edges [][]int) int {
parent := make([]int, n)
size := make([]int, n)
for i := 0; i < n; i++ {
parent[i] = i
size[i] = 1
}
find := func(node int) int {
for node != parent[node] {
parent[node] = parent[parent[node]]
node = parent[node]
}
return node
}
components := n
for _, edge := range edges {
rootA := find(edge[0])
rootB := find(edge[1])
// 同一分量内的边不减少分量数量。
if rootA == rootB {
continue
}
if size[rootA] < size[rootB] {
rootA, rootB = rootB, rootA
}
// 小集合挂到大集合,只有这次成功合并才减少一个分量。
parent[rootB] = rootA
size[rootA] += size[rootB]
components--
}
return components
}
复杂度分析
- 时间复杂度:$O(n+Eα(n))$,E 为边数,α 为反阿克曼函数。
- 空间复杂度:$O(n)$,保存父节点和集合大小。
关键点总结
[!green]
- 减一对应两个分量真正合并,不对应每条输入边。
- 必须比较根,直接父节点可能不同但仍属于同一集合。
- 孤立点从初始化时就计入,无需另行统计。
易错点总结
[!yellow]
- 每条边都减一:环内的边会造成重复扣减。
- 默认父数组全部为零:错误地把节点视为同一组。
- 只统计边中出现的节点:漏掉孤立点。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 547. 省份数量 | 中等 | 连通分量目标相同,原题输入邻接矩阵,本题输入边列表。 |
| 200. 岛屿数量 | 中等 | 网格相邻关系也能看作无向边,岛屿就是隐式图中的连通分量。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!