Use UnionFind to find top-level blossom of vertex
The run time should now be O(n*m*log(n))
This commit is contained in:
parent
225311dae0
commit
9ee26584ab
|
@ -34,7 +34,7 @@ def maximum_weight_matching(
|
|||
no effect on the maximum-weight matching.
|
||||
Edges with negative weight are ignored.
|
||||
|
||||
This function takes time O(n**3 + n*m*log(n)),
|
||||
This function takes time O(n * (n + m) * log(n)),
|
||||
where "n" is the number of vertices and "m" is the number of edges.
|
||||
This function uses O(n + m) memory.
|
||||
|
||||
|
@ -544,15 +544,6 @@ class _MatchingContext:
|
|||
# Initially there are no non-trivial blossoms.
|
||||
self.nontrivial_blossom: list[_NonTrivialBlossom] = []
|
||||
|
||||
# Every vertex is contained in exactly one top-level blossom
|
||||
# (possibly the trivial blossom that contains just that vertex).
|
||||
#
|
||||
# "vertex_top_blossom[x]" is the top-level blossom that contains
|
||||
# vertex "x".
|
||||
#
|
||||
# Initially all vertices are trivial top-level blossoms.
|
||||
self.vertex_top_blossom: list[_Blossom] = self.trivial_blossom.copy()
|
||||
|
||||
# "vertex_set_node[x]" represents the vertex "x" inside the
|
||||
# union-find datastructure of its top-level blossom.
|
||||
self.vertex_set_node = [b.vertex_set.insert(i, math.inf)
|
||||
|
@ -657,8 +648,8 @@ class _MatchingContext:
|
|||
def edge_slack_2x(self, e: int) -> float:
|
||||
"""Return 2 times the slack of the edge with index "e"."""
|
||||
(x, y, w) = self.graph.edges[e]
|
||||
bx = self.vertex_top_blossom[x]
|
||||
by = self.vertex_top_blossom[y]
|
||||
bx = self.vertex_set_node[x].find()
|
||||
by = self.vertex_set_node[y].find()
|
||||
return self.edge_slack_2x_info(x, y, bx, by, w)
|
||||
|
||||
#
|
||||
|
@ -930,8 +921,9 @@ class _MatchingContext:
|
|||
discovers an augmenting path. In this case it returns an alternating
|
||||
path that starts and ends in an unmatched vertex.
|
||||
|
||||
This function takes time O(k) to discover a blossom, where "k" is the
|
||||
number of sub-blossoms, or time O(n) to discover an augmenting path.
|
||||
This function takes time O(k*log(n)) to discover a blossom,
|
||||
where "k" is the number of sub-blossoms,
|
||||
or time O(n*log(n)) to discover an augmenting path.
|
||||
|
||||
Returns:
|
||||
Alternating path as an ordered list of edges between top-level
|
||||
|
@ -956,7 +948,7 @@ class _MatchingContext:
|
|||
while x != -1 or y != -1:
|
||||
|
||||
# Check if we found a common ancestor.
|
||||
bx = self.vertex_top_blossom[x]
|
||||
bx = self.vertex_set_node[x].find()
|
||||
if bx.marker:
|
||||
first_common = bx
|
||||
break
|
||||
|
@ -984,8 +976,8 @@ class _MatchingContext:
|
|||
|
||||
# If we found a common ancestor, trim the paths so they end there.
|
||||
if first_common is not None:
|
||||
assert self.vertex_top_blossom[xedges[-1][0]] is first_common
|
||||
while self.vertex_top_blossom[yedges[-1][0]] is not first_common:
|
||||
assert self.vertex_set_node[xedges[-1][0]].find() is first_common
|
||||
while self.vertex_set_node[yedges[-1][0]].find() is not first_common:
|
||||
yedges.pop()
|
||||
|
||||
# Fuse the two paths.
|
||||
|
@ -1009,7 +1001,7 @@ class _MatchingContext:
|
|||
Assign label S to the new blossom.
|
||||
Relabel all T-sub-blossoms as S and add their vertices to the queue.
|
||||
|
||||
This function takes total time O(n**2) per stage.
|
||||
This function takes total time O(n*log(n)) per stage.
|
||||
"""
|
||||
|
||||
# Check that the path is odd-length.
|
||||
|
@ -1017,12 +1009,12 @@ class _MatchingContext:
|
|||
assert len(path.edges) >= 3
|
||||
|
||||
# Construct the list of sub-blossoms (current top-level blossoms).
|
||||
subblossoms = [self.vertex_top_blossom[x] for (x, y) in path.edges]
|
||||
subblossoms = [self.vertex_set_node[x].find() for (x, y) in path.edges]
|
||||
|
||||
# Check that the path is cyclic.
|
||||
# Note the path will not always start and end with the same _vertex_,
|
||||
# but it must start and end in the same _blossom_.
|
||||
subblossoms_next = [self.vertex_top_blossom[y]
|
||||
subblossoms_next = [self.vertex_set_node[y].find()
|
||||
for (x, y) in path.edges]
|
||||
assert subblossoms[0] == subblossoms_next[-1]
|
||||
assert subblossoms[1:] == subblossoms_next[:-1]
|
||||
|
@ -1053,10 +1045,6 @@ class _MatchingContext:
|
|||
for sub in subblossoms:
|
||||
sub.parent = blossom
|
||||
|
||||
# Update blossom-membership of all vertices in the new blossom.
|
||||
for x in blossom.vertices():
|
||||
self.vertex_top_blossom[x] = blossom
|
||||
|
||||
# Merge union-find structures.
|
||||
blossom.vertex_set.merge([sub.vertex_set for sub in subblossoms])
|
||||
|
||||
|
@ -1090,7 +1078,7 @@ class _MatchingContext:
|
|||
def expand_t_blossom(self, blossom: _NonTrivialBlossom) -> None:
|
||||
"""Expand the specified T-blossom.
|
||||
|
||||
This function takes time O(n).
|
||||
This function takes total time O(n*log(n)) per stage.
|
||||
"""
|
||||
|
||||
assert blossom.parent is None
|
||||
|
@ -1116,12 +1104,6 @@ class _MatchingContext:
|
|||
assert sub.vertex_dual_offset == 0
|
||||
sub.vertex_dual_offset = vertex_dual_fixup
|
||||
|
||||
if isinstance(sub, _NonTrivialBlossom):
|
||||
for x in sub.vertices():
|
||||
self.vertex_top_blossom[x] = sub
|
||||
else:
|
||||
self.vertex_top_blossom[sub.base_vertex] = sub
|
||||
|
||||
# Insert blossom in delta2_queue if necessary.
|
||||
prio = sub.vertex_set.min_prio()
|
||||
if prio < math.inf:
|
||||
|
@ -1139,7 +1121,7 @@ class _MatchingContext:
|
|||
# the alternating tree.
|
||||
assert blossom.tree_edge is not None
|
||||
(x, y) = blossom.tree_edge
|
||||
sub = self.vertex_top_blossom[y]
|
||||
sub = self.vertex_set_node[y].find()
|
||||
|
||||
# Assign label T to that sub-blossom.
|
||||
self.assign_blossom_label_t(sub)
|
||||
|
@ -1175,7 +1157,7 @@ class _MatchingContext:
|
|||
def expand_unlabeled_blossom(self, blossom: _NonTrivialBlossom) -> None:
|
||||
"""Expand the specified unlabeled blossom.
|
||||
|
||||
This function takes time O(n).
|
||||
This function takes total time O(n*log(n)) per stage.
|
||||
"""
|
||||
|
||||
assert blossom.parent is None
|
||||
|
@ -1196,12 +1178,6 @@ class _MatchingContext:
|
|||
assert sub.vertex_dual_offset == 0
|
||||
sub.vertex_dual_offset = vertex_dual_offset
|
||||
|
||||
if isinstance(sub, _NonTrivialBlossom):
|
||||
for x in sub.vertices():
|
||||
self.vertex_top_blossom[x] = sub
|
||||
else:
|
||||
self.vertex_top_blossom[sub.base_vertex] = sub
|
||||
|
||||
# Insert blossom in delta2_queue if necessary.
|
||||
prio = sub.vertex_set.min_prio()
|
||||
if prio < math.inf:
|
||||
|
@ -1323,7 +1299,7 @@ class _MatchingContext:
|
|||
# an unmatched vertex or a blossom with unmatched base.
|
||||
assert len(path.edges) % 2 == 1
|
||||
for x in (path.edges[0][0], path.edges[-1][1]):
|
||||
b = self.vertex_top_blossom[x]
|
||||
b = self.vertex_set_node[x].find()
|
||||
assert self.vertex_mate[b.base_vertex] == -1
|
||||
|
||||
# The augmenting path looks like this:
|
||||
|
@ -1341,11 +1317,11 @@ class _MatchingContext:
|
|||
|
||||
# Augment the non-trivial blossoms on either side of this edge.
|
||||
# No action is necessary for trivial blossoms.
|
||||
bx = self.vertex_top_blossom[x]
|
||||
bx = self.vertex_set_node[x].find()
|
||||
if isinstance(bx, _NonTrivialBlossom):
|
||||
self.augment_blossom(bx, self.trivial_blossom[x])
|
||||
|
||||
by = self.vertex_top_blossom[y]
|
||||
by = self.vertex_set_node[y].find()
|
||||
if isinstance(by, _NonTrivialBlossom):
|
||||
self.augment_blossom(by, self.trivial_blossom[y])
|
||||
|
||||
|
@ -1372,7 +1348,7 @@ class _MatchingContext:
|
|||
"""
|
||||
|
||||
# Assign label S to the blossom that contains vertex "x".
|
||||
bx = self.vertex_top_blossom[x]
|
||||
bx = self.vertex_set_node[x].find()
|
||||
self.assign_blossom_label_s(bx)
|
||||
self.assign_vertex_label_s(bx)
|
||||
|
||||
|
@ -1388,7 +1364,7 @@ class _MatchingContext:
|
|||
|
||||
else:
|
||||
# Vertex "x" is matched to T-vertex "y".
|
||||
by = self.vertex_top_blossom[y]
|
||||
by = self.vertex_set_node[y].find()
|
||||
assert by.label == _LABEL_T
|
||||
|
||||
# Attach the blossom that contains "x" to the alternating tree.
|
||||
|
@ -1405,14 +1381,14 @@ class _MatchingContext:
|
|||
- "y" is an unlabeled, matched vertex.
|
||||
- There is a tight edge between vertices "x" and "y".
|
||||
"""
|
||||
assert self.vertex_top_blossom[x].label == _LABEL_S
|
||||
assert self.vertex_set_node[x].find().label == _LABEL_S
|
||||
|
||||
by = self.vertex_top_blossom[y]
|
||||
by = self.vertex_set_node[y].find()
|
||||
|
||||
# Expand zero-dual blossoms before assigning label T.
|
||||
while isinstance(by, _NonTrivialBlossom) and (by.dual_var == 0):
|
||||
self.expand_unlabeled_blossom(by)
|
||||
by = self.vertex_top_blossom[y]
|
||||
by = self.vertex_set_node[y].find()
|
||||
|
||||
# Assign label T to the unlabeled blossom.
|
||||
self.assign_blossom_label_t(by)
|
||||
|
@ -1446,7 +1422,7 @@ class _MatchingContext:
|
|||
# but not necessarily in the same vertex within that blossom.
|
||||
p = path.edges[0][0]
|
||||
q = path.edges[-1][1]
|
||||
if self.vertex_top_blossom[p] is self.vertex_top_blossom[q]:
|
||||
if self.vertex_set_node[p].find() is self.vertex_set_node[q].find():
|
||||
self.make_blossom(path)
|
||||
return None
|
||||
else:
|
||||
|
@ -1475,7 +1451,7 @@ class _MatchingContext:
|
|||
x = self.scan_queue.popleft()
|
||||
|
||||
# Double-check that "x" is an S-vertex.
|
||||
bx = self.vertex_top_blossom[x]
|
||||
bx = self.vertex_set_node[x].find()
|
||||
assert bx.label == _LABEL_S
|
||||
|
||||
# Scan the edges that are incident on "x".
|
||||
|
@ -1489,8 +1465,8 @@ class _MatchingContext:
|
|||
|
||||
# Note: blossom index of vertex "x" may change during
|
||||
# this loop, so we need to refresh it here.
|
||||
bx = self.vertex_top_blossom[x]
|
||||
by = self.vertex_top_blossom[y]
|
||||
bx = self.vertex_set_node[x].find()
|
||||
by = self.vertex_set_node[y].find()
|
||||
|
||||
# Ignore edges that are internal to a blossom.
|
||||
if bx is by:
|
||||
|
@ -1589,8 +1565,8 @@ class _MatchingContext:
|
|||
delta3_node = self.delta3_queue.find_min()
|
||||
e = delta3_node.data
|
||||
(x, y, _w) = self.graph.edges[e]
|
||||
bx = self.vertex_top_blossom[x]
|
||||
by = self.vertex_top_blossom[y]
|
||||
bx = self.vertex_set_node[x].find()
|
||||
by = self.vertex_set_node[y].find()
|
||||
assert (bx.label == _LABEL_S) and (by.label == _LABEL_S)
|
||||
if bx is not by:
|
||||
# Found edge between different top-level S-blossoms.
|
||||
|
@ -1634,7 +1610,7 @@ class _MatchingContext:
|
|||
thereby increasing the number of matched edges by 1.
|
||||
If no such path is found, the matching must already be optimal.
|
||||
|
||||
This function takes time O(n**2 + m * log(n)).
|
||||
This function takes time O((n + m) * log(n)).
|
||||
|
||||
Returns:
|
||||
True if the matching was successfully augmented.
|
||||
|
@ -1684,7 +1660,7 @@ class _MatchingContext:
|
|||
# Use the edge from S-vertex to unlabeled vertex that got
|
||||
# unlocked through the delta update.
|
||||
(x, y, _w) = self.graph.edges[delta_edge]
|
||||
if self.vertex_top_blossom[x].label != _LABEL_S:
|
||||
if self.vertex_set_node[x].find().label != _LABEL_S:
|
||||
(x, y) = (y, x)
|
||||
self.extend_tree_t(x, y)
|
||||
|
||||
|
|
Loading…
Reference in New Issue