|
| 1 | +""" |
| 2 | +This is a pure Python implementation of |
| 3 | +Gabow's algorithm for finding |
| 4 | +strongly connected components (SCCs) |
| 5 | +in a directed graph. |
| 6 | +
|
| 7 | +For doctests run: |
| 8 | + python -m doctest -v gabow_algorithm.py |
| 9 | +or |
| 10 | + python3 -m doctest -v gabow_algorithm.py |
| 11 | +For manual testing run: |
| 12 | + python gabow_algorithm.py |
| 13 | +""" |
| 14 | + |
| 15 | +from collections import defaultdict |
| 16 | +from typing import List, Dict |
| 17 | +class Graph: |
| 18 | + """ |
| 19 | + Graph data structure to represent |
| 20 | + a directed graph and find SCCs |
| 21 | + using Gabow's algorithm. |
| 22 | +
|
| 23 | + Attributes: |
| 24 | + vertices (int): Number of |
| 25 | + vertices in the graph. |
| 26 | + graph (Dict[int, List[int]]): |
| 27 | + Adjacency list of the graph. |
| 28 | +
|
| 29 | + Methods: |
| 30 | + add_edge(u, v): Adds an edge |
| 31 | + from vertex u to vertex v. |
| 32 | + find_sccs(): Finds and returns |
| 33 | + all SCCs in the graph. |
| 34 | +
|
| 35 | + Examples: |
| 36 | + >>> g = Graph(5) |
| 37 | + >>> g.add_edge(0, 2) |
| 38 | + >>> g.add_edge(2, 1) |
| 39 | + >>> g.add_edge(1, 0) |
| 40 | + >>> g.add_edge(0, 3) |
| 41 | + >>> g.add_edge(3, 4) |
| 42 | + >>> sorted(g.find_sccs()) |
| 43 | + [[0, 1, 2], [3], [4]] |
| 44 | + """ |
| 45 | + def __init__(self, vertices: int) -> None: |
| 46 | + self.vertices = vertices |
| 47 | + self.graph: Dict[int, List[int]] = defaultdict(list) |
| 48 | + self.index = 0 |
| 49 | + self.stack_s = [] # Stack S |
| 50 | + self.stack_p = [] # Stack P |
| 51 | + self.visited = [False] * vertices |
| 52 | + self.result = [] |
| 53 | + def add_edge(self, u: int, v: int) -> None: |
| 54 | + """ |
| 55 | + Adds a directed edge from vertex u to vertex v. |
| 56 | +
|
| 57 | + :param u: Starting vertex of the edge. |
| 58 | + :param v: Ending vertex of the edge. |
| 59 | + """ |
| 60 | + self.graph[u].append(v) |
| 61 | + |
| 62 | + def _dfs(self, v: int) -> None: |
| 63 | + """ |
| 64 | + Depth-first search helper function to |
| 65 | + process each vertex and identify SCCs. |
| 66 | + :param v: The current vertex to process in DFS. |
| 67 | + """ |
| 68 | + self.visited[v] = True |
| 69 | + self.stack_s.append(v) |
| 70 | + self.stack_p.append(v) |
| 71 | + |
| 72 | + for neighbor in self.graph[v]: |
| 73 | + if not self.visited[neighbor]: |
| 74 | + self._dfs(neighbor) |
| 75 | + elif neighbor in self.stack_p: |
| 76 | + while self.stack_p and self.stack_p[-1] != neighbor: |
| 77 | + self.stack_p.pop() |
| 78 | + if self.stack_p and self.stack_p[-1] == v: |
| 79 | + scc = [] |
| 80 | + while True: |
| 81 | + node = self.stack_s.pop() |
| 82 | + scc.append(node) |
| 83 | + if node == v: |
| 84 | + break |
| 85 | + self.stack_p.pop() |
| 86 | + self.result.append(scc) |
| 87 | + def find_sccs(self) -> List[List[int]]: |
| 88 | + """ |
| 89 | + Finds all strongly connected components |
| 90 | + in the directed graph. |
| 91 | + :return: List of SCCs, where each SCC |
| 92 | + is represented as a list of vertices. |
| 93 | + """ |
| 94 | + for v in range(self.vertices): |
| 95 | + if not self.visited[v]: |
| 96 | + self._dfs(v) |
| 97 | + return self.result |
| 98 | +if __name__ == "__main__": |
| 99 | + import doctest |
| 100 | + doctest.testmod() |
| 101 | + # Example usage for manual testing |
| 102 | + try: |
| 103 | + vertex_count = int(input("Enter the number of vertices: ")) |
| 104 | + g = Graph(vertex_count) |
| 105 | + edge_count = int(input("Enter the number of edges: ")) |
| 106 | + print("Enter each edge as a pair of vertices (u v):") |
| 107 | + for _ in range(edge_count): |
| 108 | + u, v = map(int, input().split()) |
| 109 | + g.add_edge(u, v) |
| 110 | + sccs = g.find_sccs() |
| 111 | + print("Strongly Connected Components:", sccs) |
| 112 | + except ValueError: |
| 113 | + print("Invalid input. Please enter valid integers.") |
0 commit comments