Mini Shell
"""Implements functionality to find biconnected components."""
from collections import deque, defaultdict
from .connected_components import get_connected_components_as_subgraphs
from ..helpers import get_subgraph_from_edge_list
def find_biconnected_components(graph):
"""Finds all the biconnected components in a graph.
Returns a list of lists, each containing the edges that form a biconnected component.
Returns an empty list for an empty graph.
"""
list_of_components = []
# Run the algorithm on each of the connected components of the graph
components = get_connected_components_as_subgraphs(graph)
for component in components:
# --Call the internal biconnnected components function to find
# --the edge lists for this particular connected component
edge_list = _internal_get_biconnected_components_edge_lists(component)
list_of_components.extend(edge_list)
return list_of_components
def find_biconnected_components_as_subgraphs(graph):
"""Finds the biconnected components and returns them as subgraphs."""
list_of_graphs = []
list_of_components = find_biconnected_components(graph)
for edge_list in list_of_components:
subgraph = get_subgraph_from_edge_list(graph, edge_list)
list_of_graphs.append(subgraph)
return list_of_graphs
def find_articulation_vertices(graph):
"""Finds all of the articulation vertices within a graph.
Returns a list of all articulation vertices within the graph.
Returns an empty list for an empty graph.
"""
articulation_vertices = []
all_nodes = graph.get_all_node_ids()
if len(all_nodes) == 0:
return articulation_vertices
# Run the algorithm on each of the connected components of the graph
components = get_connected_components_as_subgraphs(graph)
for component in components:
# --Call the internal articulation vertices function to find
# --the node list for this particular connected component
vertex_list = _internal_get_cut_vertex_list(component)
articulation_vertices.extend(vertex_list)
return articulation_vertices
# Helper functions
def _internal_get_biconnected_components_edge_lists(graph):
"""Works on a single connected component to produce the edge lists of the biconnected components.
Returns a list of lists, each containing the edges that combine to produce the connected component.
Returns a single nested list with 1 edge if there is only 1 edge in the graph (a 2-node graph is a
special case, generally considered to be a biconnected graph).
Returns an empty list if there are no edges in the graph (i.e. if it's a single-node or empty graph).
"""
list_of_components = []
num_nodes = graph.num_nodes()
num_edges = graph.num_edges()
if num_nodes <= 2:
if num_edges == 1:
list_of_components.append(graph.get_all_edge_ids())
return list_of_components
dfs_count = 0
edge_stack = deque()
dfs_stack = deque()
visited = defaultdict(lambda: False)
parent = defaultdict(lambda: None)
depth = {}
low = {}
preorder_processed = defaultdict(lambda: False)
postorder_processed = defaultdict(lambda: False)
# We're simulating a recursive DFS with an explicit stack, since Python has a really small function stack
unvisited_nodes = set(graph.get_all_node_ids())
while len(unvisited_nodes) > 0:
# --Initialize the first stack frame, simulating the DFS call on the root node
u = unvisited_nodes.pop()
parent[u] = u
stack_frame = {
'u': u,
'v': None,
'remaining_children': graph.neighbors(u)
}
dfs_stack.appendleft(stack_frame)
while len(dfs_stack) > 0:
frame = dfs_stack.popleft()
u = frame['u']
v = frame['v']
if not visited[u]:
if u in unvisited_nodes:
unvisited_nodes.remove(u)
visited[u] = True
dfs_count += 1
depth[u] = dfs_count
low[u] = depth[u]
if len(frame['remaining_children']) > 0:
v = frame['remaining_children'].pop()
frame['v'] = v
if v is None:
# --u has no neighbor nodes
continue
if not preorder_processed[v]:
# --This is the preorder processing, done for each neighbor node ''v'' of u
node_v = graph.get_node(v)
for edge_id in node_v['edges']:
edge = graph.get_edge(edge_id)
tpl_a = (u, v)
tpl_b = (v, u)
if tpl_a == edge['vertices'] or tpl_b == edge['vertices']:
edge_stack.appendleft(edge_id)
break
parent[v] = u
preorder_processed[v] = True
# print 'preorder for {}'.format(v)
dfs_stack.appendleft(frame)
# --Simulate the recursion to call the DFS on v
new_frame = {
'u': v,
'v': None,
'remaining_children': graph.neighbors(v)
}
dfs_stack.appendleft(new_frame)
continue
elif not postorder_processed[v] and u == parent[v]:
# --This is the postorder processing, done for each neighbor node ''v'' of u
if low[v] >= depth[u]:
component = output_component(graph, edge_stack, u, v)
if len(component) > 2:
# --You can't have a biconnected component with less than 3 edges
list_of_components.append(component)
low[u] = min(low[u], low[v])
postorder_processed[v] = True
# print 'postorder for {}'.format(v)
elif visited[v] and (parent[u] != v) and (depth[v] < depth[u]):
# (u,v) is a backedge from u to its ancestor v
node_v = graph.get_node(v)
for edge_id in node_v['edges']:
edge = graph.get_edge(edge_id)
tpl_a = (u, v)
tpl_b = (v, u)
if tpl_a == edge['vertices'] or tpl_b == edge['vertices']:
edge_stack.appendleft(edge_id)
break
low[u] = min(low[u], depth[v])
if len(frame['remaining_children']) > 0:
# --Continue onto the next neighbor node of u
v = frame['remaining_children'].pop()
frame['v'] = v
dfs_stack.appendleft(frame)
return list_of_components
def output_component(graph, edge_stack, u, v):
"""Helper function to pop edges off the stack and produce a list of them."""
edge_list = []
while len(edge_stack) > 0:
edge_id = edge_stack.popleft()
edge_list.append(edge_id)
edge = graph.get_edge(edge_id)
tpl_a = (u, v)
tpl_b = (v, u)
if tpl_a == edge['vertices'] or tpl_b == edge['vertices']:
break
return edge_list
def _internal_get_cut_vertex_list(graph):
"""Works on a single connected component to produce the node list of cut vertices.
Returns a list of nodes.
Returns an empty list if there are no nodes in the graph (i.e. if it's an empty graph).
"""
list_of_cut_vertices = set()
if graph.num_nodes() == 0:
return list(list_of_cut_vertices)
dfs_count = 0
root_dfs_count = 1
dfs_stack = deque()
visited = defaultdict(lambda: False)
parent = defaultdict(lambda: None)
children = defaultdict(lambda: [])
depth = {}
low = {}
preorder_processed = defaultdict(lambda: False)
postorder_processed = defaultdict(lambda: False)
# We're simulating a recursive DFS with an explicit stack, since Python has a really small function stack
unvisited_nodes = set(graph.get_all_node_ids())
while len(unvisited_nodes) > 0:
# --Initialize the first stack frame, simulating the DFS call on the root node
u = unvisited_nodes.pop()
parent[u] = u
stack_frame = {
'u': u,
'v': None,
'remaining_children': graph.neighbors(u)
}
dfs_stack.appendleft(stack_frame)
while len(dfs_stack) > 0:
frame = dfs_stack.popleft()
u = frame['u']
v = frame['v']
if not visited[u]:
if u in unvisited_nodes:
unvisited_nodes.remove(u)
visited[u] = True
dfs_count += 1
depth[u] = dfs_count
low[u] = depth[u]
if len(frame['remaining_children']) > 0:
v = frame['remaining_children'].pop()
frame['v'] = v
if v is None:
# --u has no neighbor nodes
continue
if not preorder_processed[v]:
# --This is the preorder processing, done for each neighbor node ''v'' of u
parent[v] = u
children[u].append(v)
preorder_processed[v] = True
# print 'preorder for {}'.format(v)
dfs_stack.appendleft(frame)
# --Simulate the recursion to call the DFS on v
new_frame = {
'u': v,
'v': None,
'remaining_children': graph.neighbors(v)
}
dfs_stack.appendleft(new_frame)
continue
elif not postorder_processed[v] and u == parent[v]:
# --This is the postorder processing, done for each neighbor node ''v'' of u
if low[v] >= depth[u] and depth[u] > 1:
list_of_cut_vertices.add(u)
low[u] = min(low[u], low[v])
postorder_processed[v] = True
# print 'postorder for {}'.format(v)
elif visited[v] and (parent[u] != v) and (depth[v] < depth[u]):
# (u,v) is a backedge from u to its ancestor v
low[u] = min(low[u], depth[v])
if len(frame['remaining_children']) > 0:
# --Continue onto the next neighbor node of u
v = frame['remaining_children'].pop()
frame['v'] = v
dfs_stack.appendleft(frame)
# The root node gets special treatment; it's a cut vertex iff it has multiple children
if len(children[root_dfs_count]) > 1:
for node_id, dfs in list(depth.items()):
if dfs == root_dfs_count:
list_of_cut_vertices.add(node_id)
break
return list(list_of_cut_vertices)
Zerion Mini Shell 1.0