原文: https://www.programiz.com/dsa/strongly-connected-components

在本教程中,您将学习如何形成强连通的组件。 此外,您还将在 C,C++ ,Java 和 Python 中找到 kosararju 算法的工作示例。

强连通的组件是有向图的一部分,其中从每个顶点到另一个顶点都有一条路径。 仅适用于有向图

例如:

让我们来看看下图。

强连通的组件 - 图1

初始的图

上图的强连通的组件是:

强连通的组件 - 图2

强连通组件

您可以观察到,在第一个强连接的组件中,每个顶点都可以通过定向路径到达另一个顶点。

可以使用 Kosaraju 算法找到这些组件。


Kosaraju 算法

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

涉及三个步骤。

  1. 在整个图上执行深度优先搜索。
    让我们从顶点 0 开始,访问其所有子顶点,并将访问的顶点标记为完成。 如果顶点通向已经访问过的顶点,则将该顶点推入栈。
    例如:从顶点 0 开始,转到顶点 1,顶点 2,然后到达顶点 3。 顶点 3 导致已经访问过的顶点 0,因此将源顶点(即顶点 3)压入栈。
    强连通的组件 - 图3
    图上的 DFS
    转到上一个顶点(顶点 2),并访问其子顶点,即顶点 4,顶点 5,顶点 6 和顶点 7 顺序。 由于顶点 7 无处可去,因此将其推入栈。
    强连通的组件 - 图4
    图上的 DFS 转到上一个顶点(顶点 6),并访问其子顶点。 但是,它的所有子顶点都已访问,因此将其推入栈。
    强连通的组件 - 图5
    堆叠
    类似地,创建最终堆叠。
    强连通的组件 - 图6
    最终筹码

  2. 反转原始图。
    强连通的组件 - 图7
    反转图上的 DFS

  3. 对反向图执行深度优先搜索。
    从栈的顶部顶点开始。 遍历其所有子顶点。 一旦到达已经访问过的顶点,就会形成一个强连通的组件。
    例如:从栈中弹出顶点 0。 从顶点 0 开始,遍历其子顶点(依次为顶点 0,顶点 1,顶点 2,顶点 3)并将它们标记为已访问。 顶点 3 的子级已经被访问过,因此这些访问过的顶点形成一个强连接的组件。
    强连通的组件 - 图8
    从顶部开始,并遍历所有顶点
    转到栈并弹出顶部顶点(如果已访问)。 否则,请从栈中选择顶部顶点,然后遍历其子顶点,如上所示。
    强连通的组件 - 图9
    如果已经访问过,则弹出顶部顶点
    强连通的组件 - 图10
    强连通的组件

  4. 因此,强连接的组件为:
    强连通的组件 - 图11
    所有强连接的组件


Python,Java,C++ 示例

  1. # Kosaraju's algorithm to find strongly connected components in Python
  2. from collections import defaultdict
  3. class Graph:
  4. def __init__(self, vertex):
  5. self.V = vertex
  6. self.graph = defaultdict(list)
  7. # Add edge into the graph
  8. def add_edge(self, s, d):
  9. self.graph[s].append(d)
  10. # dfs
  11. def dfs(self, d, visited_vertex):
  12. visited_vertex[d] = True
  13. print(d, end='')
  14. for i in self.graph[d]:
  15. if not visited_vertex[i]:
  16. self.dfs(i, visited_vertex)
  17. def fill_order(self, d, visited_vertex, stack):
  18. visited_vertex[d] = True
  19. for i in self.graph[d]:
  20. if not visited_vertex[i]:
  21. self.fill_order(i, visited_vertex, stack)
  22. stack = stack.append(d)
  23. # transpose the matrix
  24. def transpose(self):
  25. g = Graph(self.V)
  26. for i in self.graph:
  27. for j in self.graph[i]:
  28. g.add_edge(j, i)
  29. return g
  30. # Print stongly connected components
  31. def print_scc(self):
  32. stack = []
  33. visited_vertex = [False] * (self.V)
  34. for i in range(self.V):
  35. if not visited_vertex[i]:
  36. self.fill_order(i, visited_vertex, stack)
  37. gr = self.transpose()
  38. visited_vertex = [False] * (self.V)
  39. while stack:
  40. i = stack.pop()
  41. if not visited_vertex[i]:
  42. gr.dfs(i, visited_vertex)
  43. print("")
  44. g = Graph(8)
  45. g.add_edge(0, 1)
  46. g.add_edge(1, 2)
  47. g.add_edge(2, 3)
  48. g.add_edge(2, 4)
  49. g.add_edge(3, 0)
  50. g.add_edge(4, 5)
  51. g.add_edge(5, 6)
  52. g.add_edge(6, 4)
  53. g.add_edge(6, 7)
  54. print("Strongly Connected Components:")
  55. g.print_scc()
  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. }
  1. // Kosaraju's algorithm to find strongly connected components in C++
  2. #include <iostream>
  3. #include <list>
  4. #include <stack>
  5. using namespace std;
  6. class Graph {
  7. int V;
  8. list<int> *adj;
  9. void fillOrder(int s, bool visitedV[], stack<int> &Stack);
  10. void DFS(int s, bool visitedV[]);
  11. public:
  12. Graph(int V);
  13. void addEdge(int s, int d);
  14. void printSCC();
  15. Graph transpose();
  16. };
  17. Graph::Graph(int V) {
  18. this->V = V;
  19. adj = new list<int>[V];
  20. }
  21. // DFS
  22. void Graph::DFS(int s, bool visitedV[]) {
  23. visitedV[s] = true;
  24. cout << s << " ";
  25. list<int>::iterator i;
  26. for (i = adj[s].begin(); i != adj[s].end(); ++i)
  27. if (!visitedV[*i])
  28. DFS(*i, visitedV);
  29. }
  30. // Transpose
  31. Graph Graph::transpose() {
  32. Graph g(V);
  33. for (int s = 0; s < V; s++) {
  34. list<int>::iterator i;
  35. for (i = adj[s].begin(); i != adj[s].end(); ++i) {
  36. g.adj[*i].push_back(s);
  37. }
  38. }
  39. return g;
  40. }
  41. // Add edge into the graph
  42. void Graph::addEdge(int s, int d) {
  43. adj[s].push_back(d);
  44. }
  45. void Graph::fillOrder(int s, bool visitedV[], stack<int> &Stack) {
  46. visitedV[s] = true;
  47. list<int>::iterator i;
  48. for (i = adj[s].begin(); i != adj[s].end(); ++i)
  49. if (!visitedV[*i])
  50. fillOrder(*i, visitedV, Stack);
  51. Stack.push(s);
  52. }
  53. // Print strongly connected component
  54. void Graph::printSCC() {
  55. stack<int> Stack;
  56. bool *visitedV = new bool[V];
  57. for (int i = 0; i < V; i++)
  58. visitedV[i] = false;
  59. for (int i = 0; i < V; i++)
  60. if (visitedV[i] == false)
  61. fillOrder(i, visitedV, Stack);
  62. Graph gr = transpose();
  63. for (int i = 0; i < V; i++)
  64. visitedV[i] = false;
  65. while (Stack.empty() == false) {
  66. int s = Stack.top();
  67. Stack.pop();
  68. if (visitedV[s] == false) {
  69. gr.DFS(s, visitedV);
  70. cout << endl;
  71. }
  72. }
  73. }
  74. int main() {
  75. Graph g(8);
  76. g.addEdge(0, 1);
  77. g.addEdge(1, 2);
  78. g.addEdge(2, 3);
  79. g.addEdge(2, 4);
  80. g.addEdge(3, 0);
  81. g.addEdge(4, 5);
  82. g.addEdge(5, 6);
  83. g.addEdge(6, 4);
  84. g.addEdge(6, 7);
  85. cout << "Strongly Connected Components:\n";
  86. g.printSCC();
  87. }

Kosaraju 算法复杂度

Kosaraju 算法在线性时间即O(V+E)中运行。


强连通的组件应用

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