Spaces:
Running
Running
"""Biconnected components and articulation points.""" | |
from itertools import chain | |
import networkx as nx | |
from networkx.utils.decorators import not_implemented_for | |
__all__ = [ | |
"biconnected_components", | |
"biconnected_component_edges", | |
"is_biconnected", | |
"articulation_points", | |
] | |
def is_biconnected(G): | |
"""Returns True if the graph is biconnected, False otherwise. | |
A graph is biconnected if, and only if, it cannot be disconnected by | |
removing only one node (and all edges incident on that node). If | |
removing a node increases the number of disconnected components | |
in the graph, that node is called an articulation point, or cut | |
vertex. A biconnected graph has no articulation points. | |
Parameters | |
---------- | |
G : NetworkX Graph | |
An undirected graph. | |
Returns | |
------- | |
biconnected : bool | |
True if the graph is biconnected, False otherwise. | |
Raises | |
------ | |
NetworkXNotImplemented | |
If the input graph is not undirected. | |
Examples | |
-------- | |
>>> G = nx.path_graph(4) | |
>>> print(nx.is_biconnected(G)) | |
False | |
>>> G.add_edge(0, 3) | |
>>> print(nx.is_biconnected(G)) | |
True | |
See Also | |
-------- | |
biconnected_components | |
articulation_points | |
biconnected_component_edges | |
is_strongly_connected | |
is_weakly_connected | |
is_connected | |
is_semiconnected | |
Notes | |
----- | |
The algorithm to find articulation points and biconnected | |
components is implemented using a non-recursive depth-first-search | |
(DFS) that keeps track of the highest level that back edges reach | |
in the DFS tree. A node `n` is an articulation point if, and only | |
if, there exists a subtree rooted at `n` such that there is no | |
back edge from any successor of `n` that links to a predecessor of | |
`n` in the DFS tree. By keeping track of all the edges traversed | |
by the DFS we can obtain the biconnected components because all | |
edges of a bicomponent will be traversed consecutively between | |
articulation points. | |
References | |
---------- | |
.. [1] Hopcroft, J.; Tarjan, R. (1973). | |
"Efficient algorithms for graph manipulation". | |
Communications of the ACM 16: 372–378. doi:10.1145/362248.362272 | |
""" | |
bccs = biconnected_components(G) | |
try: | |
bcc = next(bccs) | |
except StopIteration: | |
# No bicomponents (empty graph?) | |
return False | |
try: | |
next(bccs) | |
except StopIteration: | |
# Only one bicomponent | |
return len(bcc) == len(G) | |
else: | |
# Multiple bicomponents | |
return False | |
def biconnected_component_edges(G): | |
"""Returns a generator of lists of edges, one list for each biconnected | |
component of the input graph. | |
Biconnected components are maximal subgraphs such that the removal of a | |
node (and all edges incident on that node) will not disconnect the | |
subgraph. Note that nodes may be part of more than one biconnected | |
component. Those nodes are articulation points, or cut vertices. | |
However, each edge belongs to one, and only one, biconnected component. | |
Notice that by convention a dyad is considered a biconnected component. | |
Parameters | |
---------- | |
G : NetworkX Graph | |
An undirected graph. | |
Returns | |
------- | |
edges : generator of lists | |
Generator of lists of edges, one list for each bicomponent. | |
Raises | |
------ | |
NetworkXNotImplemented | |
If the input graph is not undirected. | |
Examples | |
-------- | |
>>> G = nx.barbell_graph(4, 2) | |
>>> print(nx.is_biconnected(G)) | |
False | |
>>> bicomponents_edges = list(nx.biconnected_component_edges(G)) | |
>>> len(bicomponents_edges) | |
5 | |
>>> G.add_edge(2, 8) | |
>>> print(nx.is_biconnected(G)) | |
True | |
>>> bicomponents_edges = list(nx.biconnected_component_edges(G)) | |
>>> len(bicomponents_edges) | |
1 | |
See Also | |
-------- | |
is_biconnected, | |
biconnected_components, | |
articulation_points, | |
Notes | |
----- | |
The algorithm to find articulation points and biconnected | |
components is implemented using a non-recursive depth-first-search | |
(DFS) that keeps track of the highest level that back edges reach | |
in the DFS tree. A node `n` is an articulation point if, and only | |
if, there exists a subtree rooted at `n` such that there is no | |
back edge from any successor of `n` that links to a predecessor of | |
`n` in the DFS tree. By keeping track of all the edges traversed | |
by the DFS we can obtain the biconnected components because all | |
edges of a bicomponent will be traversed consecutively between | |
articulation points. | |
References | |
---------- | |
.. [1] Hopcroft, J.; Tarjan, R. (1973). | |
"Efficient algorithms for graph manipulation". | |
Communications of the ACM 16: 372–378. doi:10.1145/362248.362272 | |
""" | |
yield from _biconnected_dfs(G, components=True) | |
def biconnected_components(G): | |
"""Returns a generator of sets of nodes, one set for each biconnected | |
component of the graph | |
Biconnected components are maximal subgraphs such that the removal of a | |
node (and all edges incident on that node) will not disconnect the | |
subgraph. Note that nodes may be part of more than one biconnected | |
component. Those nodes are articulation points, or cut vertices. The | |
removal of articulation points will increase the number of connected | |
components of the graph. | |
Notice that by convention a dyad is considered a biconnected component. | |
Parameters | |
---------- | |
G : NetworkX Graph | |
An undirected graph. | |
Returns | |
------- | |
nodes : generator | |
Generator of sets of nodes, one set for each biconnected component. | |
Raises | |
------ | |
NetworkXNotImplemented | |
If the input graph is not undirected. | |
Examples | |
-------- | |
>>> G = nx.lollipop_graph(5, 1) | |
>>> print(nx.is_biconnected(G)) | |
False | |
>>> bicomponents = list(nx.biconnected_components(G)) | |
>>> len(bicomponents) | |
2 | |
>>> G.add_edge(0, 5) | |
>>> print(nx.is_biconnected(G)) | |
True | |
>>> bicomponents = list(nx.biconnected_components(G)) | |
>>> len(bicomponents) | |
1 | |
You can generate a sorted list of biconnected components, largest | |
first, using sort. | |
>>> G.remove_edge(0, 5) | |
>>> [len(c) for c in sorted(nx.biconnected_components(G), key=len, reverse=True)] | |
[5, 2] | |
If you only want the largest connected component, it's more | |
efficient to use max instead of sort. | |
>>> Gc = max(nx.biconnected_components(G), key=len) | |
To create the components as subgraphs use: | |
``(G.subgraph(c).copy() for c in biconnected_components(G))`` | |
See Also | |
-------- | |
is_biconnected | |
articulation_points | |
biconnected_component_edges | |
k_components : this function is a special case where k=2 | |
bridge_components : similar to this function, but is defined using | |
2-edge-connectivity instead of 2-node-connectivity. | |
Notes | |
----- | |
The algorithm to find articulation points and biconnected | |
components is implemented using a non-recursive depth-first-search | |
(DFS) that keeps track of the highest level that back edges reach | |
in the DFS tree. A node `n` is an articulation point if, and only | |
if, there exists a subtree rooted at `n` such that there is no | |
back edge from any successor of `n` that links to a predecessor of | |
`n` in the DFS tree. By keeping track of all the edges traversed | |
by the DFS we can obtain the biconnected components because all | |
edges of a bicomponent will be traversed consecutively between | |
articulation points. | |
References | |
---------- | |
.. [1] Hopcroft, J.; Tarjan, R. (1973). | |
"Efficient algorithms for graph manipulation". | |
Communications of the ACM 16: 372–378. doi:10.1145/362248.362272 | |
""" | |
for comp in _biconnected_dfs(G, components=True): | |
yield set(chain.from_iterable(comp)) | |
def articulation_points(G): | |
"""Yield the articulation points, or cut vertices, of a graph. | |
An articulation point or cut vertex is any node whose removal (along with | |
all its incident edges) increases the number of connected components of | |
a graph. An undirected connected graph without articulation points is | |
biconnected. Articulation points belong to more than one biconnected | |
component of a graph. | |
Notice that by convention a dyad is considered a biconnected component. | |
Parameters | |
---------- | |
G : NetworkX Graph | |
An undirected graph. | |
Yields | |
------ | |
node | |
An articulation point in the graph. | |
Raises | |
------ | |
NetworkXNotImplemented | |
If the input graph is not undirected. | |
Examples | |
-------- | |
>>> G = nx.barbell_graph(4, 2) | |
>>> print(nx.is_biconnected(G)) | |
False | |
>>> len(list(nx.articulation_points(G))) | |
4 | |
>>> G.add_edge(2, 8) | |
>>> print(nx.is_biconnected(G)) | |
True | |
>>> len(list(nx.articulation_points(G))) | |
0 | |
See Also | |
-------- | |
is_biconnected | |
biconnected_components | |
biconnected_component_edges | |
Notes | |
----- | |
The algorithm to find articulation points and biconnected | |
components is implemented using a non-recursive depth-first-search | |
(DFS) that keeps track of the highest level that back edges reach | |
in the DFS tree. A node `n` is an articulation point if, and only | |
if, there exists a subtree rooted at `n` such that there is no | |
back edge from any successor of `n` that links to a predecessor of | |
`n` in the DFS tree. By keeping track of all the edges traversed | |
by the DFS we can obtain the biconnected components because all | |
edges of a bicomponent will be traversed consecutively between | |
articulation points. | |
References | |
---------- | |
.. [1] Hopcroft, J.; Tarjan, R. (1973). | |
"Efficient algorithms for graph manipulation". | |
Communications of the ACM 16: 372–378. doi:10.1145/362248.362272 | |
""" | |
seen = set() | |
for articulation in _biconnected_dfs(G, components=False): | |
if articulation not in seen: | |
seen.add(articulation) | |
yield articulation | |
def _biconnected_dfs(G, components=True): | |
# depth-first search algorithm to generate articulation points | |
# and biconnected components | |
visited = set() | |
for start in G: | |
if start in visited: | |
continue | |
discovery = {start: 0} # time of first discovery of node during search | |
low = {start: 0} | |
root_children = 0 | |
visited.add(start) | |
edge_stack = [] | |
stack = [(start, start, iter(G[start]))] | |
edge_index = {} | |
while stack: | |
grandparent, parent, children = stack[-1] | |
try: | |
child = next(children) | |
if grandparent == child: | |
continue | |
if child in visited: | |
if discovery[child] <= discovery[parent]: # back edge | |
low[parent] = min(low[parent], discovery[child]) | |
if components: | |
edge_index[parent, child] = len(edge_stack) | |
edge_stack.append((parent, child)) | |
else: | |
low[child] = discovery[child] = len(discovery) | |
visited.add(child) | |
stack.append((parent, child, iter(G[child]))) | |
if components: | |
edge_index[parent, child] = len(edge_stack) | |
edge_stack.append((parent, child)) | |
except StopIteration: | |
stack.pop() | |
if len(stack) > 1: | |
if low[parent] >= discovery[grandparent]: | |
if components: | |
ind = edge_index[grandparent, parent] | |
yield edge_stack[ind:] | |
del edge_stack[ind:] | |
else: | |
yield grandparent | |
low[grandparent] = min(low[parent], low[grandparent]) | |
elif stack: # length 1 so grandparent is root | |
root_children += 1 | |
if components: | |
ind = edge_index[grandparent, parent] | |
yield edge_stack[ind:] | |
del edge_stack[ind:] | |
if not components: | |
# root node is articulation point if it has more than 1 child | |
if root_children > 1: | |
yield start | |