在大屏或电脑上体验更佳——动画和操作区域更宽敞。
输入

连通分量

无向图的连通分量是一组能沿路径互相到达的顶点,并且无法再加入其他顶点。一轮 DFS 或 BFS 只能找到起点所在的分量。要数清全部分量,还需从头扫描所有顶点,在每个未访问顶点启动一次填充。总时间为 O(V + E),启动次数就是分量数。

外层循环会跳过很多顶点,这不是多余工作:它们已经由先前的填充归入某个分量。扫描到尚未着色的顶点,才说明遇到了新分量。若只从 0 搜索一次,就会漏掉其余区域。

为什么需要外层扫描

外层用 for start in range(n) 逐个检查顶点。已有颜色就跳过;没有颜色就创建新分量,从该点填充全部可达顶点,再继续扫描。

本课的填充采用显式栈实现 DFS,并在入栈时着色。先压入并标记起点,然后反复出栈,给尚未着色的邻居标上当前分量颜色并入栈,直到栈空。

无向图的可达性对称:若能从 a 到 b,也能返回。因此从一个分量中的任意顶点开始,都能填满整个分量。换成队列做 BFS,内部着色顺序会改变,分量划分不会变;划分由图决定。

这种方法适合静态无向图中的网络分组、网格岛屿和相关项。若边不断加入,Union-Find 可以增量维护同一划分。若要求有向图中顶点互相可达,则要计算强连通分量,例如使用两遍扫描的 Kosaraju 或一遍 DFS 的 Tarjan,不能直接套用这里的填充。

连通分量逐步演示

默认图有八个顶点:三角形 {0, 1, 2} 的边为 0–1、1–2、2–0;顶点对 {3, 4} 之间有边 3–4;链 {5, 6, 7} 的边为 5–6、6–7。外层按 0 到 7 扫描。

  1. 到达未着色的 0,创建分量 #1。标记并压入 0;弹出后标记、压入 1 和 2。先弹出 2,再弹出 1,它们的邻居都已归入 #1,栈清空,得到 {0, 1, 2}。
  2. 到达 1,已在 #1,跳过。
  3. 到达 2,已在 #1,跳过。它们在外层扫描到来前就由填充完成了归属。
  4. 到达未着色的 3,创建 #2。压入 3,弹出后标记并压入 4;弹出 4 时邻居 3 已着色。栈空,得到 {3, 4}。
  5. 到达 4,已在 #2,跳过。
  6. 到达未着色的 5,创建 #3。依次从 5 填充到 6、7。7 的邻居 6 已着色,栈空,得到 {5, 6, 7}。
  7. 到达 6,已在 #3,跳过。
  8. 到达 7,已在 #3,跳过,扫描结束。

结果为 3 个分量。外层迭代 8 次,启动填充 3 次,跳过 5 次。每个顶点在外层扫描时,要么成为新分量起点,要么已经着色而被跳过,两种情况互斥且覆盖全部顶点。

一个连通图预设只启动一次填充,其余迭代都跳过。分散预设包含孤立顶点:即使没有边,也会单独启动填充。全部孤立预设把这一点推到极限,没有任何边,启动 n 次填充,跳过零次。

时间与空间复杂度

情况时间空间
全图O(V + E)O(V)

外层检查 V 个顶点。所有填充合计,每个顶点入栈、出栈各一次,每条无向边从两个端点各检查一次,因此不是每个分量各付一次全图成本,总量为 O(V + E)。

访问颜色和栈占 O(V) 空间。栈最坏可保存 O(V) 个待处理顶点,例如弹出星形图中心后,其余顶点同时入栈。这里的显式栈在长链上不一定很大,不能和递归调用栈混为一谈。

边界情况

  • 单个顶点构成一个分量,填充没有邻居便结束。
  • 孤立顶点没有边,仍是大小为一的分量,外层必须检查它。
  • 所有顶点连通时,启动一次填充,其余全部跳过。
  • 全部顶点孤立时,有 n 个分量,是最大数量,每个顶点都启动填充。

常见错误

  1. 只运行一次 DFS/BFS。它只能找出起点所在分量,必须在外层重启搜索。
  2. 忽略无边顶点,导致数量和划分都不正确。
  3. 出栈时才标记。同一顶点可能在首次出栈前被多次压入,增大栈,某些写法还会算错数量。本实现入栈就标记。
  4. 混淆连通分量和强连通分量。有向边不保证相互可达,需要 Kosaraju、Tarjan 等 SCC 算法。
  5. 认为 DFS 和 BFS 得到不同分量。它们改变探索顺序,不改变无向图的连通划分。

这些课程共享同一个技巧,或正好接到下一个概念。

锁标表示交互演示需付费。所有讲解文档均可免费阅读。

浏览主题

发现错误或有不清楚的地方? 提交反馈。