diff --git a/pydatastructs/graphs/__init__.py b/pydatastructs/graphs/__init__.py index 751c645bc..1e2eebfe1 100644 --- a/pydatastructs/graphs/__init__.py +++ b/pydatastructs/graphs/__init__.py @@ -11,7 +11,8 @@ breadth_first_search, breadth_first_search_parallel, minimum_spanning_tree, - minimum_spanning_tree_parallel + minimum_spanning_tree_parallel, + strongly_connected_components ) __all__.extend(algorithms.__all__) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 91854726c..a325c2396 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -15,7 +15,8 @@ 'breadth_first_search', 'breadth_first_search_parallel', 'minimum_spanning_tree', - 'minimum_spanning_tree_parallel' + 'minimum_spanning_tree_parallel', + 'strongly_connected_components' ] def breadth_first_search( @@ -445,3 +446,105 @@ def minimum_spanning_tree_parallel(graph, algorithm, num_threads): "isn't implemented for finding minimum spanning trees." %(algorithm, graph._impl)) return getattr(algorithms, func)(graph, num_threads) + +def _visit(graph, vertex, visited, incoming, L): + stack = [vertex] + while stack: + top = stack[-1] + if not visited.get(top, False): + visited[top] = True + for node in graph.neighbors(top): + if incoming.get(node.name, None) is None: + incoming[node.name] = [] + incoming[node.name].append(top) + if not visited.get(node.name, False): + stack.append(node.name) + if top is stack[-1]: + L.append(stack.pop()) + +def _assign(graph, u, incoming, assigned, component): + stack = [u] + while stack: + top = stack[-1] + if not assigned.get(top, False): + assigned[top] = True + component.add(top) + for u in incoming[top]: + if not assigned.get(u, False): + stack.append(u) + if top is stack[-1]: + stack.pop() + +def _strongly_connected_components_kosaraju_adjacency_list(graph): + visited, incoming, L = dict(), dict(), [] + for u in graph.vertices: + if not visited.get(u, False): + _visit(graph, u, visited, incoming, L) + + assigned = dict() + components = [] + for i in range(-1, -len(L) - 1, -1): + comp = set() + if not assigned.get(L[i], False): + _assign(graph, L[i], incoming, assigned, comp) + if comp: + components.append(comp) + + return components + +_strongly_connected_components_kosaraju_adjacency_matrix = \ + _strongly_connected_components_kosaraju_adjacency_list + +def strongly_connected_components(graph, algorithm): + """ + Computes strongly connected components for the given + graph and algorithm. + + Parameters + ========== + + graph: Graph + The graph whose minimum spanning tree + has to be computed. + algorithm: str + The algorithm which should be used for + computing strongly connected components. + Currently the following algorithms are + supported, + 'kosaraju' -> Kosaraju's algorithm as given in + [1]. + + Returns + ======= + + components: list + Python list with each element as set of vertices. + + Examples + ======== + + >>> from pydatastructs import Graph, AdjacencyListGraphNode + >>> from pydatastructs import strongly_connected_components + >>> v1, v2, v3 = [AdjacencyListGraphNode(i) for i in range(3)] + >>> g = Graph(v1, v2, v3) + >>> g.add_edge(v1.name, v2.name) + >>> g.add_edge(v2.name, v3.name) + >>> g.add_edge(v3.name, v1.name) + >>> scc = strongly_connected_components(g, 'kosaraju') + >>> scc == [{'2', '0', '1'}] + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm + + """ + import pydatastructs.graphs.algorithms as algorithms + func = "_strongly_connected_components_" + algorithm + "_" + graph._impl + if not hasattr(algorithms, func): + raise NotImplementedError( + "Currently %s algoithm for %s implementation of graphs " + "isn't implemented for finding strongly connected components." + %(algorithm, graph._impl)) + return getattr(algorithms, func)(graph) diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index 3b43c7eef..8ada73cc8 100644 --- a/pydatastructs/graphs/tests/test_algorithms.py +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -1,6 +1,6 @@ from pydatastructs import (breadth_first_search, Graph, breadth_first_search_parallel, minimum_spanning_tree, -minimum_spanning_tree_parallel) +minimum_spanning_tree_parallel, strongly_connected_components) def test_breadth_first_search(): @@ -155,3 +155,33 @@ def _test_minimum_spanning_tree(func, ds, algorithm, *args): _test_minimum_spanning_tree(fmstp, "List", "kruskal", 3) _test_minimum_spanning_tree(fmstp, "Matrix", "kruskal", 3) _test_minimum_spanning_tree(fmstp, "List", "prim", 3) + +def test_strongly_connected_components(): + + def _test_strongly_connected_components(func, ds, algorithm, *args): + import pydatastructs.utils.misc_util as utils + GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode") + a, b, c, d, e, f, g, h = \ + [GraphNode(chr(x)) for x in range(ord('a'), ord('h') + 1)] + graph = Graph(a, b, c, d, e, f, g, h) + graph.add_edge(a.name, b.name) + graph.add_edge(b.name, c.name) + graph.add_edge(b.name, f.name) + graph.add_edge(b.name, e.name) + graph.add_edge(c.name, d.name) + graph.add_edge(c.name, g.name) + graph.add_edge(d.name, h.name) + graph.add_edge(d.name, c.name) + graph.add_edge(e.name, f.name) + graph.add_edge(e.name, a.name) + graph.add_edge(f.name, g.name) + graph.add_edge(g.name, f.name) + graph.add_edge(h.name, d.name) + graph.add_edge(h.name, g.name) + comps = func(graph, algorithm) + expected_comps = [{'e', 'a', 'b'}, {'d', 'c', 'h'}, {'g', 'f'}] + assert comps == expected_comps + + scc = strongly_connected_components + _test_strongly_connected_components(scc, "List", "kosaraju") + _test_strongly_connected_components(scc, "Matrix", "kosaraju")