Spaces:
Running
Running
File size: 5,375 Bytes
b200bda |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
"""
Stoer-Wagner minimum cut algorithm.
"""
from itertools import islice
import networkx as nx
from ...utils import BinaryHeap, arbitrary_element, not_implemented_for
__all__ = ["stoer_wagner"]
@not_implemented_for("directed")
@not_implemented_for("multigraph")
@nx._dispatch(edge_attrs="weight")
def stoer_wagner(G, weight="weight", heap=BinaryHeap):
r"""Returns the weighted minimum edge cut using the Stoer-Wagner algorithm.
Determine the minimum edge cut of a connected graph using the
Stoer-Wagner algorithm. In weighted cases, all weights must be
nonnegative.
The running time of the algorithm depends on the type of heaps used:
============== =============================================
Type of heap Running time
============== =============================================
Binary heap $O(n (m + n) \log n)$
Fibonacci heap $O(nm + n^2 \log n)$
Pairing heap $O(2^{2 \sqrt{\log \log n}} nm + n^2 \log n)$
============== =============================================
Parameters
----------
G : NetworkX graph
Edges of the graph are expected to have an attribute named by the
weight parameter below. If this attribute is not present, the edge is
considered to have unit weight.
weight : string
Name of the weight attribute of the edges. If the attribute is not
present, unit weight is assumed. Default value: 'weight'.
heap : class
Type of heap to be used in the algorithm. It should be a subclass of
:class:`MinHeap` or implement a compatible interface.
If a stock heap implementation is to be used, :class:`BinaryHeap` is
recommended over :class:`PairingHeap` for Python implementations without
optimized attribute accesses (e.g., CPython) despite a slower
asymptotic running time. For Python implementations with optimized
attribute accesses (e.g., PyPy), :class:`PairingHeap` provides better
performance. Default value: :class:`BinaryHeap`.
Returns
-------
cut_value : integer or float
The sum of weights of edges in a minimum cut.
partition : pair of node lists
A partitioning of the nodes that defines a minimum cut.
Raises
------
NetworkXNotImplemented
If the graph is directed or a multigraph.
NetworkXError
If the graph has less than two nodes, is not connected or has a
negative-weighted edge.
Examples
--------
>>> G = nx.Graph()
>>> G.add_edge("x", "a", weight=3)
>>> G.add_edge("x", "b", weight=1)
>>> G.add_edge("a", "c", weight=3)
>>> G.add_edge("b", "c", weight=5)
>>> G.add_edge("b", "d", weight=4)
>>> G.add_edge("d", "e", weight=2)
>>> G.add_edge("c", "y", weight=2)
>>> G.add_edge("e", "y", weight=3)
>>> cut_value, partition = nx.stoer_wagner(G)
>>> cut_value
4
"""
n = len(G)
if n < 2:
raise nx.NetworkXError("graph has less than two nodes.")
if not nx.is_connected(G):
raise nx.NetworkXError("graph is not connected.")
# Make a copy of the graph for internal use.
G = nx.Graph(
(u, v, {"weight": e.get(weight, 1)}) for u, v, e in G.edges(data=True) if u != v
)
for u, v, e in G.edges(data=True):
if e["weight"] < 0:
raise nx.NetworkXError("graph has a negative-weighted edge.")
cut_value = float("inf")
nodes = set(G)
contractions = [] # contracted node pairs
# Repeatedly pick a pair of nodes to contract until only one node is left.
for i in range(n - 1):
# Pick an arbitrary node u and create a set A = {u}.
u = arbitrary_element(G)
A = {u}
# Repeatedly pick the node "most tightly connected" to A and add it to
# A. The tightness of connectivity of a node not in A is defined by the
# of edges connecting it to nodes in A.
h = heap() # min-heap emulating a max-heap
for v, e in G[u].items():
h.insert(v, -e["weight"])
# Repeat until all but one node has been added to A.
for j in range(n - i - 2):
u = h.pop()[0]
A.add(u)
for v, e in G[u].items():
if v not in A:
h.insert(v, h.get(v, 0) - e["weight"])
# A and the remaining node v define a "cut of the phase". There is a
# minimum cut of the original graph that is also a cut of the phase.
# Due to contractions in earlier phases, v may in fact represent
# multiple nodes in the original graph.
v, w = h.min()
w = -w
if w < cut_value:
cut_value = w
best_phase = i
# Contract v and the last node added to A.
contractions.append((u, v))
for w, e in G[v].items():
if w != u:
if w not in G[u]:
G.add_edge(u, w, weight=e["weight"])
else:
G[u][w]["weight"] += e["weight"]
G.remove_node(v)
# Recover the optimal partitioning from the contractions.
G = nx.Graph(islice(contractions, best_phase))
v = contractions[best_phase][1]
G.add_node(v)
reachable = set(nx.single_source_shortest_path_length(G, v))
partition = (list(reachable), list(nodes - reachable))
return cut_value, partition
|