206 lines
5.9 KiB
Python
206 lines
5.9 KiB
Python
"""Bridge-finding algorithms."""
|
|
from itertools import chain
|
|
|
|
import networkx as nx
|
|
from networkx.utils import not_implemented_for
|
|
|
|
__all__ = ["bridges", "has_bridges", "local_bridges"]
|
|
|
|
|
|
@not_implemented_for("directed")
|
|
@nx._dispatchable
|
|
def bridges(G, root=None):
|
|
"""Generate all bridges in a graph.
|
|
|
|
A *bridge* in a graph is an edge whose removal causes the number of
|
|
connected components of the graph to increase. Equivalently, a bridge is an
|
|
edge that does not belong to any cycle. Bridges are also known as cut-edges,
|
|
isthmuses, or cut arcs.
|
|
|
|
Parameters
|
|
----------
|
|
G : undirected graph
|
|
|
|
root : node (optional)
|
|
A node in the graph `G`. If specified, only the bridges in the
|
|
connected component containing this node will be returned.
|
|
|
|
Yields
|
|
------
|
|
e : edge
|
|
An edge in the graph whose removal disconnects the graph (or
|
|
causes the number of connected components to increase).
|
|
|
|
Raises
|
|
------
|
|
NodeNotFound
|
|
If `root` is not in the graph `G`.
|
|
|
|
NetworkXNotImplemented
|
|
If `G` is a directed graph.
|
|
|
|
Examples
|
|
--------
|
|
The barbell graph with parameter zero has a single bridge:
|
|
|
|
>>> G = nx.barbell_graph(10, 0)
|
|
>>> list(nx.bridges(G))
|
|
[(9, 10)]
|
|
|
|
Notes
|
|
-----
|
|
This is an implementation of the algorithm described in [1]_. An edge is a
|
|
bridge if and only if it is not contained in any chain. Chains are found
|
|
using the :func:`networkx.chain_decomposition` function.
|
|
|
|
The algorithm described in [1]_ requires a simple graph. If the provided
|
|
graph is a multigraph, we convert it to a simple graph and verify that any
|
|
bridges discovered by the chain decomposition algorithm are not multi-edges.
|
|
|
|
Ignoring polylogarithmic factors, the worst-case time complexity is the
|
|
same as the :func:`networkx.chain_decomposition` function,
|
|
$O(m + n)$, where $n$ is the number of nodes in the graph and $m$ is
|
|
the number of edges.
|
|
|
|
References
|
|
----------
|
|
.. [1] https://en.wikipedia.org/wiki/Bridge_%28graph_theory%29#Bridge-Finding_with_Chain_Decompositions
|
|
"""
|
|
multigraph = G.is_multigraph()
|
|
H = nx.Graph(G) if multigraph else G
|
|
chains = nx.chain_decomposition(H, root=root)
|
|
chain_edges = set(chain.from_iterable(chains))
|
|
H_copy = H.copy()
|
|
if root is not None:
|
|
H = H.subgraph(nx.node_connected_component(H, root)).copy()
|
|
for u, v in H.edges():
|
|
if (u, v) not in chain_edges and (v, u) not in chain_edges:
|
|
if multigraph and len(G[u][v]) > 1:
|
|
continue
|
|
yield u, v
|
|
|
|
|
|
@not_implemented_for("directed")
|
|
@nx._dispatchable
|
|
def has_bridges(G, root=None):
|
|
"""Decide whether a graph has any bridges.
|
|
|
|
A *bridge* in a graph is an edge whose removal causes the number of
|
|
connected components of the graph to increase.
|
|
|
|
Parameters
|
|
----------
|
|
G : undirected graph
|
|
|
|
root : node (optional)
|
|
A node in the graph `G`. If specified, only the bridges in the
|
|
connected component containing this node will be considered.
|
|
|
|
Returns
|
|
-------
|
|
bool
|
|
Whether the graph (or the connected component containing `root`)
|
|
has any bridges.
|
|
|
|
Raises
|
|
------
|
|
NodeNotFound
|
|
If `root` is not in the graph `G`.
|
|
|
|
NetworkXNotImplemented
|
|
If `G` is a directed graph.
|
|
|
|
Examples
|
|
--------
|
|
The barbell graph with parameter zero has a single bridge::
|
|
|
|
>>> G = nx.barbell_graph(10, 0)
|
|
>>> nx.has_bridges(G)
|
|
True
|
|
|
|
On the other hand, the cycle graph has no bridges::
|
|
|
|
>>> G = nx.cycle_graph(5)
|
|
>>> nx.has_bridges(G)
|
|
False
|
|
|
|
Notes
|
|
-----
|
|
This implementation uses the :func:`networkx.bridges` function, so
|
|
it shares its worst-case time complexity, $O(m + n)$, ignoring
|
|
polylogarithmic factors, where $n$ is the number of nodes in the
|
|
graph and $m$ is the number of edges.
|
|
|
|
"""
|
|
try:
|
|
next(bridges(G, root=root))
|
|
except StopIteration:
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
|
|
@not_implemented_for("multigraph")
|
|
@not_implemented_for("directed")
|
|
@nx._dispatchable(edge_attrs="weight")
|
|
def local_bridges(G, with_span=True, weight=None):
|
|
"""Iterate over local bridges of `G` optionally computing the span
|
|
|
|
A *local bridge* is an edge whose endpoints have no common neighbors.
|
|
That is, the edge is not part of a triangle in the graph.
|
|
|
|
The *span* of a *local bridge* is the shortest path length between
|
|
the endpoints if the local bridge is removed.
|
|
|
|
Parameters
|
|
----------
|
|
G : undirected graph
|
|
|
|
with_span : bool
|
|
If True, yield a 3-tuple `(u, v, span)`
|
|
|
|
weight : function, string or None (default: None)
|
|
If function, used to compute edge weights for the span.
|
|
If string, the edge data attribute used in calculating span.
|
|
If None, all edges have weight 1.
|
|
|
|
Yields
|
|
------
|
|
e : edge
|
|
The local bridges as an edge 2-tuple of nodes `(u, v)` or
|
|
as a 3-tuple `(u, v, span)` when `with_span is True`.
|
|
|
|
Raises
|
|
------
|
|
NetworkXNotImplemented
|
|
If `G` is a directed graph or multigraph.
|
|
|
|
Examples
|
|
--------
|
|
A cycle graph has every edge a local bridge with span N-1.
|
|
|
|
>>> G = nx.cycle_graph(9)
|
|
>>> (0, 8, 8) in set(nx.local_bridges(G))
|
|
True
|
|
"""
|
|
if with_span is not True:
|
|
for u, v in G.edges:
|
|
if not (set(G[u]) & set(G[v])):
|
|
yield u, v
|
|
else:
|
|
wt = nx.weighted._weight_function(G, weight)
|
|
for u, v in G.edges:
|
|
if not (set(G[u]) & set(G[v])):
|
|
enodes = {u, v}
|
|
|
|
def hide_edge(n, nbr, d):
|
|
if n not in enodes or nbr not in enodes:
|
|
return wt(n, nbr, d)
|
|
return None
|
|
|
|
try:
|
|
span = nx.shortest_path_length(G, u, v, weight=hide_edge)
|
|
yield u, v, span
|
|
except nx.NetworkXNoPath:
|
|
yield u, v, float("inf")
|