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