-
-
Notifications
You must be signed in to change notification settings - Fork 46.8k
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
Changes from all commits
f3fe173
96cab02
c682fcc
cc00fcb
c4a3b3a
8d57fee
2e302ff
1995b9a
dfaf1af
1a8f04b
298800a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 = { | ||
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 = [] | ||
|
||
cclauss marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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] | ||
|
||
cclauss marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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))} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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] | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() | ||
cclauss marked this conversation as resolved.
Show resolved
Hide resolved
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.