31
loading...
This website collects cookies to deliver better user experience
def dfs(node):
print(node.val)
for child in node.children:
dfs(child)
1->2->3->1
, calling dfs(node_1)
will loop forever. Let's fix this by keeping track of which nodes have already been visited by the DFS.def dfs(node):
if node in visited:
return None
print(node.val)
visited.add(node)
for child in node.children:
dfs(child)
visited = set()
1 -> 2 -> 3
, calling dfs(2)
will miss node 1
. So let's assume we have a dictionary edges
, where edges[node]
lists all the children of node
.def print_nodes(edges):
def dfs(node):
if node in visited:
return None
print(node.val)
visited.add(node)
for child in edges[node]:
dfs(child)
visited = set()
for node in edges.keys():
dfs(node)
1
/
2
/ \
3 4
1,2,3,4
(assuming 3 comes before 4 in edges[node_2]
). This would be a "pre-order" way of printing the graph. The "post-order" way would bedef print_nodes_post(edges):
def dfs(node):
if node in visited:
return None
visited.add(node)
for child in edges[node]:
dfs(child)
print(node)
visited = set()
for node in edges.keys():
dfs(node)
3,4,2,1
.edges.keys()
). The point is that DFS allows us to explore a whole graph, and we can perform tasks as we move along.def dfs(node):
visited.add(node)
do_some_work_pre_children(node)
for child in edges[node]:
dfs(child)
do_some_work_post_children(node)
def is_acyclic(edges):
def dfs(node):
if node in visited:
# cycle detected?
return False
visited.add(node)
for child in edges[node]:
if dfs(child) is False:
return False
return True
visited = set()
for node in edges.keys():
if node not in visited:
if dfs(node) is False:
return False
return True
dfs
the node is already in visited
then a cycle must be present. However this is deeply flawed. For example with the graph1
/ \
2 3
\ /
4
False
, ie that the graph contains a cycle.visited
set, which is specific to the recursion.def is_acyclic(edges):
def dfs(node):
if node in visited:
return True
visited.add(node)
visited_dfs.add(node)
for child in edges[node]:
if child in visited_dfs:
return False
if dfs(child) is False:
return False
# after we explored node and all its children
# we can remove the temporary marker
# if we don't we'll run into the same issue
# as in the naive approach
visited_dfs.remove(node)
return True
visited = set()
visited_dfs = set()
for node in edges.keys():
if dfs(node) is False:
return False
return True
a < b
in the order.1->2->3->1
it will be incompatible with the graph structure. However if we have a DAG (directed acyclic graph), ie a graph without cycles, then we can. One way to come up with such an order is via DFS. For the graph0
|
1
/ \
2 3
\ /
4
5
[0,1,2,3,4,5]
, [0,1,3,2,4,5]
, [0,5,1,2,3,4]
are all acceptable orders. The idea is to explore the graph using DFS and to add nodes to the list in post-order. For example let's start by exploring the graph above from the node 2. This will yield the list [4,2]
. Continuing from node 1, we will have [4,2,3,1]
. Continue with node 5 to get [4,2,3,1,5]
, and finally add 0 to get [4,2,3,1,5,0]
. Reversing this list we get [0,5,1,3,2,4]
, which is a valid total order.def topological_sort(edges):
"""Assumes graph is acyclic."""
def dfs(node):
if node in visited:
return None
visited.add(node)
for child in edges[node]:
dfs(child)
order.append(node)
order = []
visited = set()
for node in edges.keys():
dfs(node)
order.reverse()
return order
def topological_sort_if_acyclic(edges):
"""Return an order if acyclic, else return []"""
def dfs(node):
if node in visited:
return True
visited.add(node)
visited_dfs.add(node)
for child in edges[node]:
if child in visited_dfs:
return False
if dfs(child) is False:
return False
visited_dfs.remove(node)
order.append(node)
return True
order = []
visited = set()
visited_dfs = set()
for node in edges.keys():
if dfs(node) is False:
return []
order.reverse()
return order
31