Skip to content

Strongly connected components #2114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 17, 2020
2 changes: 2 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,13 +257,15 @@
* [Page Rank](https://github.com/TheAlgorithms/Python/blob/master/graphs/page_rank.py)
* [Prim](https://github.com/TheAlgorithms/Python/blob/master/graphs/prim.py)
* [Scc Kosaraju](https://github.com/TheAlgorithms/Python/blob/master/graphs/scc_kosaraju.py)
* [Strongly Connected Components](https://github.com/TheAlgorithms/Python/blob/master/graphs/strongly_connected_components.py)
* [Tarjans Scc](https://github.com/TheAlgorithms/Python/blob/master/graphs/tarjans_scc.py)

## Greedy Method
* [Greedy Knapsack](https://github.com/TheAlgorithms/Python/blob/master/greedy_method/greedy_knapsack.py)
* [Test Knapsack](https://github.com/TheAlgorithms/Python/blob/master/greedy_method/test_knapsack.py)

## Hashes
* [Adler32](https://github.com/TheAlgorithms/Python/blob/master/hashes/adler32.py)
* [Chaos Machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/chaos_machine.py)
* [Enigma Machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/enigma_machine.py)
* [Hamming Code](https://github.com/TheAlgorithms/Python/blob/master/hashes/hamming_code.py)
Expand Down
105 changes: 105 additions & 0 deletions graphs/strongly_connected_components.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""
https://en.wikipedia.org/wiki/Strongly_connected_component
Finding strongly connected components in directed graph
"""

test_graph_1 = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cclauss Do you have any idea how we can avoid declaring these variables here but keep current doctests?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are OK here. Naming helps a lot so when I see test_graph_1, I immediately know that this data is for testing purposes. If I was going to move this algorithm to some larger program, I will need to do some level of refactoring. I might move all the doctests and the test data to a separate test_xyz.py file or I might remove them altogether. For our purposes in this repo, a single file that contains the algorithm, tests, and test data is good for learning purposes as long as we are diligent with our naming.

0: [2, 3],
1: [0],
2: [1],
3: [4],
4: [],
}

test_graph_2 = {
0: [1, 2, 3],
1: [2],
2: [0],
3: [4],
4: [5],
5: [3],
}


def topology_sort(graph: dict, vert: int, visited: list) -> list:
"""
Use depth first search to sort graph
At this time graph is the same as input
>>> topology_sort(test_graph_1, 0, 5 * [False])
[1, 2, 4, 3, 0]
>>> topology_sort(test_graph_2, 0, 6 * [False])
[2, 1, 5, 4, 3, 0]
"""

visited[vert] = True
order = []

for neighbour in graph[vert]:
if not visited[neighbour]:
order += topology_sort(graph, neighbour, visited)

order.append(vert)

return order


def find_components(reversed_graph: dict, vert: int, visited: list) -> list:
"""
Use depth first search to find strongliy connected
vertices. Now graph is reversed
>>> find_components({0: [1], 1: [2], 2: [0]}, 0, 5 * [False])
[0, 1, 2]
>>> find_components({0: [2], 1: [0], 2: [0, 1]}, 0, 6 * [False])
[0, 2, 1]
"""

visited[vert] = True
component = [vert]

for neighbour in reversed_graph[vert]:
if not visited[neighbour]:
component += find_components(reversed_graph, neighbour, visited)

return component


def strongly_connected_components(graph: dict) -> list:
"""
This function takes graph as a parameter
and then returns the list of strongly connected components
>>> strongly_connected_components(test_graph_1)
[[0, 1, 2], [3], [4]]
>>> strongly_connected_components(test_graph_2)
[[0, 2, 1], [3, 5, 4]]
"""

visited = len(graph) * [False]
reversed_graph = {vert: [] for vert in range(len(graph))}

Copy link
Member

@l3str4nge l3str4nge Jun 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reverse_graph --> reversed_graph . I think comment above is unnecessary. Variable name defines exactly what is it.

for vert, neighbours in graph.items():
for neighbour in neighbours:
reversed_graph[neighbour].append(vert)

order = []
for i, was_visited in enumerate(visited):
if not was_visited:
order += topology_sort(graph, i, visited)

components_list = []
visited = len(graph) * [False]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comments, rename variables better.

for i in range(len(graph)):
vert = order[len(graph) - i - 1]
if not visited[vert]:
component = find_components(reversed_graph, vert, visited)
components_list.append(component)

return components_list


if __name__ == "__main__":
import doctest

doctest.testmod()