bridges.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. """Bridge-finding algorithms."""
  2. from itertools import chain
  3. import networkx as nx
  4. from networkx.utils import not_implemented_for
  5. __all__ = ["bridges", "has_bridges", "local_bridges"]
  6. @not_implemented_for("directed")
  7. @nx._dispatchable
  8. def bridges(G, root=None):
  9. """Generate all bridges in a graph.
  10. A *bridge* in a graph is an edge whose removal causes the number of
  11. connected components of the graph to increase. Equivalently, a bridge is an
  12. edge that does not belong to any cycle. Bridges are also known as cut-edges,
  13. isthmuses, or cut arcs.
  14. Parameters
  15. ----------
  16. G : undirected graph
  17. root : node (optional)
  18. A node in the graph `G`. If specified, only the bridges in the
  19. connected component containing this node will be returned.
  20. Yields
  21. ------
  22. e : edge
  23. An edge in the graph whose removal disconnects the graph (or
  24. causes the number of connected components to increase).
  25. Raises
  26. ------
  27. NodeNotFound
  28. If `root` is not in the graph `G`.
  29. NetworkXNotImplemented
  30. If `G` is a directed graph.
  31. Examples
  32. --------
  33. The barbell graph with parameter zero has a single bridge:
  34. >>> G = nx.barbell_graph(10, 0)
  35. >>> list(nx.bridges(G))
  36. [(9, 10)]
  37. Notes
  38. -----
  39. This is an implementation of the algorithm described in [1]_. An edge is a
  40. bridge if and only if it is not contained in any chain. Chains are found
  41. using the :func:`networkx.chain_decomposition` function.
  42. The algorithm described in [1]_ requires a simple graph. If the provided
  43. graph is a multigraph, we convert it to a simple graph and verify that any
  44. bridges discovered by the chain decomposition algorithm are not multi-edges.
  45. Ignoring polylogarithmic factors, the worst-case time complexity is the
  46. same as the :func:`networkx.chain_decomposition` function,
  47. $O(m + n)$, where $n$ is the number of nodes in the graph and $m$ is
  48. the number of edges.
  49. References
  50. ----------
  51. .. [1] https://en.wikipedia.org/wiki/Bridge_%28graph_theory%29#Bridge-Finding_with_Chain_Decompositions
  52. """
  53. multigraph = G.is_multigraph()
  54. H = nx.Graph(G) if multigraph else G
  55. chains = nx.chain_decomposition(H, root=root)
  56. chain_edges = set(chain.from_iterable(chains))
  57. if root is not None:
  58. H = H.subgraph(nx.node_connected_component(H, root)).copy()
  59. for u, v in H.edges():
  60. if (u, v) not in chain_edges and (v, u) not in chain_edges:
  61. if multigraph and len(G[u][v]) > 1:
  62. continue
  63. yield u, v
  64. @not_implemented_for("directed")
  65. @nx._dispatchable
  66. def has_bridges(G, root=None):
  67. """Decide whether a graph has any bridges.
  68. A *bridge* in a graph is an edge whose removal causes the number of
  69. connected components of the graph to increase.
  70. Parameters
  71. ----------
  72. G : undirected graph
  73. root : node (optional)
  74. A node in the graph `G`. If specified, only the bridges in the
  75. connected component containing this node will be considered.
  76. Returns
  77. -------
  78. bool
  79. Whether the graph (or the connected component containing `root`)
  80. has any bridges.
  81. Raises
  82. ------
  83. NodeNotFound
  84. If `root` is not in the graph `G`.
  85. NetworkXNotImplemented
  86. If `G` is a directed graph.
  87. Examples
  88. --------
  89. The barbell graph with parameter zero has a single bridge::
  90. >>> G = nx.barbell_graph(10, 0)
  91. >>> nx.has_bridges(G)
  92. True
  93. On the other hand, the cycle graph has no bridges::
  94. >>> G = nx.cycle_graph(5)
  95. >>> nx.has_bridges(G)
  96. False
  97. Notes
  98. -----
  99. This implementation uses the :func:`networkx.bridges` function, so
  100. it shares its worst-case time complexity, $O(m + n)$, ignoring
  101. polylogarithmic factors, where $n$ is the number of nodes in the
  102. graph and $m$ is the number of edges.
  103. """
  104. try:
  105. next(bridges(G, root=root))
  106. except StopIteration:
  107. return False
  108. else:
  109. return True
  110. @not_implemented_for("multigraph")
  111. @not_implemented_for("directed")
  112. @nx._dispatchable(edge_attrs="weight")
  113. def local_bridges(G, with_span=True, weight=None):
  114. """Iterate over local bridges of `G` optionally computing the span
  115. A *local bridge* is an edge whose endpoints have no common neighbors.
  116. That is, the edge is not part of a triangle in the graph.
  117. The *span* of a *local bridge* is the shortest path length between
  118. the endpoints if the local bridge is removed.
  119. Parameters
  120. ----------
  121. G : undirected graph
  122. with_span : bool
  123. If True, yield a 3-tuple `(u, v, span)`
  124. weight : function, string or None (default: None)
  125. If function, used to compute edge weights for the span.
  126. If string, the edge data attribute used in calculating span.
  127. If None, all edges have weight 1.
  128. Yields
  129. ------
  130. e : edge
  131. The local bridges as an edge 2-tuple of nodes `(u, v)` or
  132. as a 3-tuple `(u, v, span)` when `with_span is True`.
  133. Raises
  134. ------
  135. NetworkXNotImplemented
  136. If `G` is a directed graph or multigraph.
  137. Examples
  138. --------
  139. A cycle graph has every edge a local bridge with span N-1.
  140. >>> G = nx.cycle_graph(9)
  141. >>> (0, 8, 8) in set(nx.local_bridges(G))
  142. True
  143. """
  144. if with_span is not True:
  145. for u, v in G.edges:
  146. if not (set(G[u]) & set(G[v])):
  147. yield u, v
  148. else:
  149. wt = nx.weighted._weight_function(G, weight)
  150. for u, v in G.edges:
  151. if not (set(G[u]) & set(G[v])):
  152. enodes = {u, v}
  153. def hide_edge(n, nbr, d):
  154. if n not in enodes or nbr not in enodes:
  155. return wt(n, nbr, d)
  156. return None
  157. try:
  158. span = nx.shortest_path_length(G, u, v, weight=hide_edge)
  159. yield u, v, span
  160. except nx.NetworkXNoPath:
  161. yield u, v, float("inf")