什么是强连通分量

强连通分量是有向图的一部分。

  • 两个顶点强连通:在有向图G中,如果两个顶点vi, vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从 vj 到 vi 的有向路径,则称两个顶点强连通(strongly connected)。
  • 强连通图:如果一个有向图G,对于其中任意两个顶点v,u,都存在从v到u以及从u到v的有向路径,则称G为强连通图。
  • 强连通分量:有向图G的极大强连通子图S,即添加任意顶点都会导致S失去强连通的属性,则称S为G的强连通分量。

例如:
让我们来看看下面的图表。

强连通分量 - 图1
初始图

上图的强连通分量是:

强连通分量 - 图2
强连通分量

可以观察到,在第一个强连通分量中,每个顶点都可以通过有向路径到达另一个顶点。这些分量可以使用Kosaraju’s Algorithm找到。

Kosaraju 算法图解

Kosaraju’s Algorithm 基于两次实现的深度优先搜索算法。

涉及三个步骤:

  1. 对整个图执行深度优先搜索。

    让我们从顶点 0 开始,访问它的所有子顶点,并将访问过的顶点标记为完成。如果一个顶点指向一个已经访问过的顶点,则将该顶点推入堆栈。
    例如:从顶点0开始,到顶点1,顶点2,再到顶点3。Vertex-3 导致已经访问过的 vertex-0,因此将源顶点(即 vertex-3)压入堆栈。

强连通分量 - 图3
图上的DFS

转到前一个顶点(vertex-2)并依次访问其子顶点即vertex-4、vertex-5、vertex-6和vertex-7。由于顶点 7 无处可去,将其压入堆栈。

强连通分量 - 图4
图上的DFS

转到上一个顶点(vertex-6)并访问其子顶点。但是,它的所有子顶点都被访问了,所以将它压入堆栈。

强连通分量 - 图5
堆叠

类似地,创建了最终堆栈。

强连通分量 - 图6
最终堆栈
  1. 反转原始图形。 | 强连通分量 - 图7 | | —- | | 反向图上的 DFS |
  1. 在反转图上执行深度优先搜索。

从堆栈的顶部顶点开始。遍历其所有子顶点。一旦到达已经访问过的顶点,就形成了一个强连通分量。
例如:从堆栈中弹出顶点 0。从顶点 0 开始,遍历其子顶点(依次为顶点 0、顶点 1、顶点 2、顶点 3)并将它们标记为已访问。顶点 3 的子节点已经被访问过,所以这些访问过的顶点形成一个强连接组件。

强连通分量 - 图8
从顶部开始,遍历所有顶点

如果已经访问过,则转到堆栈并弹出顶部顶点。否则,从堆栈中选择顶部顶点并遍历其子顶点,如上所示。

强连通分量 - 图9
如果已经访问过,则弹出顶部顶点
强连通分量 - 图10
强连通分量
  1. 因此,强连通分量是: | 强连通分量 - 图11 | | —- | | 所有强连通分量 |

Kosaraju 的复杂性

  • Kosaraju 的算法在线性时间内运行,即O(V+E)。

强连接组件的应用

  • 车辆路径应用
  • 地图
  • 形式验证中的模型检查

Kosaraju 的实现

  1. // Kosaraju's algorithm to find strongly connected components in Java
  2. import java.util.*;
  3. import java.util.LinkedList;
  4. class Graph {
  5. private int V;
  6. private LinkedList<Integer> adj[];
  7. // Create a graph
  8. Graph(int s) {
  9. V = s;
  10. adj = new LinkedList[s];
  11. for (int i = 0; i < s; ++i)
  12. adj[i] = new LinkedList();
  13. }
  14. // Add edge
  15. void addEdge(int s, int d) {
  16. adj[s].add(d);
  17. }
  18. // DFS
  19. void DFSUtil(int s, boolean visitedVertices[]) {
  20. visitedVertices[s] = true;
  21. System.out.print(s + " ");
  22. int n;
  23. Iterator<Integer> i = adj[s].iterator();
  24. while (i.hasNext()) {
  25. n = i.next();
  26. if (!visitedVertices[n])
  27. DFSUtil(n, visitedVertices);
  28. }
  29. }
  30. // Transpose the graph
  31. Graph Transpose() {
  32. Graph g = new Graph(V);
  33. for (int s = 0; s < V; s++) {
  34. Iterator<Integer> i = adj[s].listIterator();
  35. while (i.hasNext())
  36. g.adj[i.next()].add(s);
  37. }
  38. return g;
  39. }
  40. void fillOrder(int s, boolean visitedVertices[], Stack stack) {
  41. visitedVertices[s] = true;
  42. Iterator<Integer> i = adj[s].iterator();
  43. while (i.hasNext()) {
  44. int n = i.next();
  45. if (!visitedVertices[n])
  46. fillOrder(n, visitedVertices, stack);
  47. }
  48. stack.push(new Integer(s));
  49. }
  50. // Print strongly connected component
  51. void printSCC() {
  52. Stack stack = new Stack();
  53. boolean visitedVertices[] = new boolean[V];
  54. for (int i = 0; i < V; i++)
  55. visitedVertices[i] = false;
  56. for (int i = 0; i < V; i++)
  57. if (visitedVertices[i] == false)
  58. fillOrder(i, visitedVertices, stack);
  59. Graph gr = Transpose();
  60. for (int i = 0; i < V; i++)
  61. visitedVertices[i] = false;
  62. while (stack.empty() == false) {
  63. int s = (int) stack.pop();
  64. if (visitedVertices[s] == false) {
  65. gr.DFSUtil(s, visitedVertices);
  66. System.out.println();
  67. }
  68. }
  69. }
  70. public static void main(String args[]) {
  71. Graph g = new Graph(8);
  72. g.addEdge(0, 1);
  73. g.addEdge(1, 2);
  74. g.addEdge(2, 3);
  75. g.addEdge(2, 4);
  76. g.addEdge(3, 0);
  77. g.addEdge(4, 5);
  78. g.addEdge(5, 6);
  79. g.addEdge(6, 4);
  80. g.addEdge(6, 7);
  81. System.out.println("Strongly Connected Components:");
  82. g.printSCC();
  83. }
  84. }