gomory_hu.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. """
  2. Gomory-Hu tree of undirected Graphs.
  3. """
  4. import networkx as nx
  5. from networkx.utils import not_implemented_for
  6. from .edmondskarp import edmonds_karp
  7. from .utils import build_residual_network
  8. default_flow_func = edmonds_karp
  9. __all__ = ["gomory_hu_tree"]
  10. @not_implemented_for("directed")
  11. def gomory_hu_tree(G, capacity="capacity", flow_func=None):
  12. r"""Returns the Gomory-Hu tree of an undirected graph G.
  13. A Gomory-Hu tree of an undirected graph with capacities is a
  14. weighted tree that represents the minimum s-t cuts for all s-t
  15. pairs in the graph.
  16. It only requires `n-1` minimum cut computations instead of the
  17. obvious `n(n-1)/2`. The tree represents all s-t cuts as the
  18. minimum cut value among any pair of nodes is the minimum edge
  19. weight in the shortest path between the two nodes in the
  20. Gomory-Hu tree.
  21. The Gomory-Hu tree also has the property that removing the
  22. edge with the minimum weight in the shortest path between
  23. any two nodes leaves two connected components that form
  24. a partition of the nodes in G that defines the minimum s-t
  25. cut.
  26. See Examples section below for details.
  27. Parameters
  28. ----------
  29. G : NetworkX graph
  30. Undirected graph
  31. capacity : string
  32. Edges of the graph G are expected to have an attribute capacity
  33. that indicates how much flow the edge can support. If this
  34. attribute is not present, the edge is considered to have
  35. infinite capacity. Default value: 'capacity'.
  36. flow_func : function
  37. Function to perform the underlying flow computations. Default value
  38. :func:`edmonds_karp`. This function performs better in sparse graphs
  39. with right tailed degree distributions.
  40. :func:`shortest_augmenting_path` will perform better in denser
  41. graphs.
  42. Returns
  43. -------
  44. Tree : NetworkX graph
  45. A NetworkX graph representing the Gomory-Hu tree of the input graph.
  46. Raises
  47. ------
  48. NetworkXNotImplemented
  49. Raised if the input graph is directed.
  50. NetworkXError
  51. Raised if the input graph is an empty Graph.
  52. Examples
  53. --------
  54. >>> G = nx.karate_club_graph()
  55. >>> nx.set_edge_attributes(G, 1, "capacity")
  56. >>> T = nx.gomory_hu_tree(G)
  57. >>> # The value of the minimum cut between any pair
  58. ... # of nodes in G is the minimum edge weight in the
  59. ... # shortest path between the two nodes in the
  60. ... # Gomory-Hu tree.
  61. ... def minimum_edge_weight_in_shortest_path(T, u, v):
  62. ... path = nx.shortest_path(T, u, v, weight="weight")
  63. ... return min((T[u][v]["weight"], (u, v)) for (u, v) in zip(path, path[1:]))
  64. >>> u, v = 0, 33
  65. >>> cut_value, edge = minimum_edge_weight_in_shortest_path(T, u, v)
  66. >>> cut_value
  67. 10
  68. >>> nx.minimum_cut_value(G, u, v)
  69. 10
  70. >>> # The Comory-Hu tree also has the property that removing the
  71. ... # edge with the minimum weight in the shortest path between
  72. ... # any two nodes leaves two connected components that form
  73. ... # a partition of the nodes in G that defines the minimum s-t
  74. ... # cut.
  75. ... cut_value, edge = minimum_edge_weight_in_shortest_path(T, u, v)
  76. >>> T.remove_edge(*edge)
  77. >>> U, V = list(nx.connected_components(T))
  78. >>> # Thus U and V form a partition that defines a minimum cut
  79. ... # between u and v in G. You can compute the edge cut set,
  80. ... # that is, the set of edges that if removed from G will
  81. ... # disconnect u from v in G, with this information:
  82. ... cutset = set()
  83. >>> for x, nbrs in ((n, G[n]) for n in U):
  84. ... cutset.update((x, y) for y in nbrs if y in V)
  85. >>> # Because we have set the capacities of all edges to 1
  86. ... # the cutset contains ten edges
  87. ... len(cutset)
  88. 10
  89. >>> # You can use any maximum flow algorithm for the underlying
  90. ... # flow computations using the argument flow_func
  91. ... from networkx.algorithms import flow
  92. >>> T = nx.gomory_hu_tree(G, flow_func=flow.boykov_kolmogorov)
  93. >>> cut_value, edge = minimum_edge_weight_in_shortest_path(T, u, v)
  94. >>> cut_value
  95. 10
  96. >>> nx.minimum_cut_value(G, u, v, flow_func=flow.boykov_kolmogorov)
  97. 10
  98. Notes
  99. -----
  100. This implementation is based on Gusfield approach [1]_ to compute
  101. Comory-Hu trees, which does not require node contractions and has
  102. the same computational complexity than the original method.
  103. See also
  104. --------
  105. :func:`minimum_cut`
  106. :func:`maximum_flow`
  107. References
  108. ----------
  109. .. [1] Gusfield D: Very simple methods for all pairs network flow analysis.
  110. SIAM J Comput 19(1):143-155, 1990.
  111. """
  112. if flow_func is None:
  113. flow_func = default_flow_func
  114. if len(G) == 0: # empty graph
  115. msg = "Empty Graph does not have a Gomory-Hu tree representation"
  116. raise nx.NetworkXError(msg)
  117. # Start the tree as a star graph with an arbitrary node at the center
  118. tree = {}
  119. labels = {}
  120. iter_nodes = iter(G)
  121. root = next(iter_nodes)
  122. for n in iter_nodes:
  123. tree[n] = root
  124. # Reuse residual network
  125. R = build_residual_network(G, capacity)
  126. # For all the leaves in the star graph tree (that is n-1 nodes).
  127. for source in tree:
  128. # Find neighbor in the tree
  129. target = tree[source]
  130. # compute minimum cut
  131. cut_value, partition = nx.minimum_cut(
  132. G, source, target, capacity=capacity, flow_func=flow_func, residual=R
  133. )
  134. labels[(source, target)] = cut_value
  135. # Update the tree
  136. # Source will always be in partition[0] and target in partition[1]
  137. for node in partition[0]:
  138. if node != source and node in tree and tree[node] == target:
  139. tree[node] = source
  140. labels[node, source] = labels.get((node, target), cut_value)
  141. #
  142. if target != root and tree[target] in partition[0]:
  143. labels[source, tree[target]] = labels[target, tree[target]]
  144. labels[target, source] = cut_value
  145. tree[source] = tree[target]
  146. tree[target] = source
  147. # Build the tree
  148. T = nx.Graph()
  149. T.add_nodes_from(G)
  150. T.add_weighted_edges_from(((u, v, labels[u, v]) for u, v in tree.items()))
  151. return T