1
0
Fork 0

Clean up management of the alternating tree

This commit is contained in:
Joris van Rantwijk 2024-06-30 13:48:11 +02:00
parent b960a85b6c
commit f35a640e43
1 changed files with 80 additions and 66 deletions

View File

@ -508,6 +508,7 @@ class _AlternatingPath(NamedTuple):
"""Represents a list of edges forming an alternating path or an """Represents a list of edges forming an alternating path or an
alternating cycle.""" alternating cycle."""
edges: list[tuple[int, int]] edges: list[tuple[int, int]]
is_cycle: bool
class _MatchingContext: class _MatchingContext:
@ -1166,7 +1167,8 @@ class _MatchingContext:
# Any S-to-S alternating path must have odd length. # Any S-to-S alternating path must have odd length.
assert len(path_edges) % 2 == 1 assert len(path_edges) % 2 == 1
return _AlternatingPath(path_edges) return _AlternatingPath(edges=path_edges,
is_cycle=(first_common is not None))
# #
# Merge and expand blossoms: # Merge and expand blossoms:
@ -1243,8 +1245,8 @@ class _MatchingContext:
blossom: _NonTrivialBlossom, blossom: _NonTrivialBlossom,
sub: _Blossom sub: _Blossom
) -> tuple[list[_Blossom], list[tuple[int, int]]]: ) -> tuple[list[_Blossom], list[tuple[int, int]]]:
"""Construct a path through the specified blossom, """Construct a path with an even number of edges through the
from sub-blossom "sub" to the base of the blossom. specified blossom, from sub-blossom "sub" to the base of "blossom".
Return: Return:
Tuple (nodes, edges). Tuple (nodes, edges).
@ -1263,6 +1265,9 @@ class _MatchingContext:
nodes = blossom.subblossoms[p:] + blossom.subblossoms[0:1] nodes = blossom.subblossoms[p:] + blossom.subblossoms[0:1]
edges = blossom.edges[p:] edges = blossom.edges[p:]
assert len(edges) % 2 == 0
assert len(nodes) % 2 == 1
return (nodes, edges) return (nodes, edges)
def expand_unlabeled_blossom(self, blossom: _NonTrivialBlossom) -> None: def expand_unlabeled_blossom(self, blossom: _NonTrivialBlossom) -> None:
@ -1352,7 +1357,7 @@ class _MatchingContext:
# Assign label S to path_nodes[p+1]. # Assign label S to path_nodes[p+1].
(y, x) = path_edges[p] (y, x) = path_edges[p]
self.extend_tree_s(x) self.extend_tree_t_to_s(x)
# Assign label T to path_nodes[i+2] and attach it # Assign label T to path_nodes[i+2] and attach it
# to path_nodes[p+1]. # to path_nodes[p+1].
@ -1504,59 +1509,49 @@ class _MatchingContext:
self.vertex_mate[y] = x self.vertex_mate[y] = x
# #
# Labeling and alternating tree expansion: # Alternating tree:
# #
def extend_tree_s(self, x: int) -> None: def extend_tree_t_to_s(self, x: int) -> None:
"""Assign label S to the unlabeled blossom that contains vertex "x". """Assign label S to the unlabeled blossom that contains vertex "x".
If vertex "x" is matched, it is attached to the alternating tree The newly labeled S-blossom is added to the alternating tree
via its matched edge. If vertex "x" is unmatched, it becomes the root via its matched edge. All vertices in the newly labeled S-blossom
of an alternating tree. are added to the scan queue.
All vertices in the newly labeled blossom are added to the scan queue. Preconditions:
- "x" is a vertex in an unlabeled blossom.
Precondition: - "x" is matched to a T-vertex via a tight edge.
"x" is an unlabeled vertex, either unmatched or matched to
a T-vertex via a tight edge.
""" """
# Assign label S to the blossom that contains vertex "x". # Assign label S to the blossom that contains vertex "x".
bx = self.vertex_set_node[x].find() bx = self.vertex_set_node[x].find()
self.assign_blossom_label_s(bx) self.assign_blossom_label_s(bx)
# Vertex "x" is matched to T-vertex "y".
y = self.vertex_mate[x] y = self.vertex_mate[x]
if y == -1: assert y != -1
# Vertex "x" is unmatched.
# It must be either a top-level vertex or the base vertex of
# a top-level blossom.
assert bx.base_vertex == x
# Mark the blossom as root of an alternating tree. by = self.vertex_set_node[y].find()
bx.tree_edge = None assert by.label == _LABEL_T
bx.tree_blossoms = {bx} assert by.tree_blossoms is not None
else: # Attach the blossom that contains "x" to the alternating tree.
# Vertex "x" is matched to T-vertex "y". bx.tree_edge = (y, x)
by = self.vertex_set_node[y].find() bx.tree_blossoms = by.tree_blossoms
assert by.label == _LABEL_T bx.tree_blossoms.add(bx)
# Attach the blossom that contains "x" to the alternating tree. def extend_tree_s_to_t(self, x: int, y: int) -> None:
bx.tree_edge = (y, x)
bx.tree_blossoms = by.tree_blossoms
assert bx.tree_blossoms is not None
bx.tree_blossoms.add(bx)
def extend_tree_t(self, x: int, y: int) -> None:
"""Assign label T to the unlabeled blossom that contains vertex "y". """Assign label T to the unlabeled blossom that contains vertex "y".
Attach it to the alternating tree via edge (x, y). The newly labeled T-blossom is added to the alternating tree.
Then immediately assign label S to the mate of vertex "y". Directly afterwards, label S is assigned to the blossom that has
a matched edge to the base of the newly labeled T-blossom, and
that newly labeled S-blossom is also added to the alternating tree.
Preconditions: Preconditions:
- "x" is an S-vertex. - "x" is an S-vertex.
- "y" is an unlabeled, matched vertex. - "y" is a vertex in an unlabeled blossom with a matched base vertex.
- There is a tight edge between vertices "x" and "y". - There is a tight edge between vertices "x" and "y".
""" """
@ -1579,36 +1574,66 @@ class _MatchingContext:
# Assign label S to the blossom that is mated to the T-blossom. # Assign label S to the blossom that is mated to the T-blossom.
z = self.vertex_mate[by.base_vertex] z = self.vertex_mate[by.base_vertex]
assert z != -1 assert z != -1
self.extend_tree_s(z) self.extend_tree_t_to_s(z)
def add_s_to_s_edge(self, x: int, y: int) -> Optional[_AlternatingPath]: def add_s_to_s_edge(self, x: int, y: int) -> bool:
"""Add the edge between S-vertices "x" and "y". """Add the edge between S-vertices "x" and "y".
If the edge connects blossoms that are part of the same alternating If the edge connects blossoms that are part of the same alternating
tree, this function creates a new S-blossom and returns None. tree, this function creates a new S-blossom and returns False.
If the edge connects two different alternating trees, an augmenting If the edge connects two different alternating trees, an augmenting
path has been discovered. In this case the function changes nothing path has been discovered. This function then augments the matching
and returns the augmenting path. and returns True. Labels are removed from blossoms that belonged
to the two alternating trees involved in the matching. All other
alternating trees and labels are preserved.
Preconditions:
- "x" and "y" are S-vertices in different top-level blossoms.
- There is a tight edge between vertices "x" and "y".
Returns: Returns:
Augmenting path if found; otherwise None. True if the matching was augmented; otherwise False.
""" """
bx = self.vertex_set_node[x].find()
by = self.vertex_set_node[y].find()
assert bx.label == _LABEL_S
assert by.label == _LABEL_S
assert bx is not by
# Trace back through the alternating trees from "x" and "y". # Trace back through the alternating trees from "x" and "y".
path = self.trace_alternating_paths(x, y) path = self.trace_alternating_paths(x, y)
# If the path is a cycle, create a new blossom. assert bx.tree_blossoms is not None
# Otherwise the path is an augmenting path. assert by.tree_blossoms is not None
# Note that an alternating starts and ends in the same blossom,
# but not necessarily in the same vertex within that blossom. if bx.tree_blossoms is by.tree_blossoms:
p = path.edges[0][0] # Both blossoms belong to the same alternating tree.
q = path.edges[-1][1] # This implies that the alternating path is a cycle.
if self.vertex_set_node[p].find() is self.vertex_set_node[q].find(): # The path will be used to create a new blossom.
assert path.is_cycle
self.make_blossom(path) self.make_blossom(path)
return None
return False
else: else:
return path # The blossoms belong to different alternating trees.
# This implies that the alternating path is an augmenting
# path between two unlabeled vertices.
# The path will be used to augment the matching.
# Delete the two alternating trees on the augmenting path.
# The blossoms in those trees become unlabeled.
self.remove_alternating_tree(bx.tree_blossoms)
self.remove_alternating_tree(by.tree_blossoms)
# Augment the matching.
assert not path.is_cycle
self.augment_matching(path)
return True
def substage_scan(self) -> None: def substage_scan(self) -> None:
"""Scan queued S-vertices and consider their incident edges. """Scan queued S-vertices and consider their incident edges.
@ -1799,25 +1824,14 @@ class _MatchingContext:
(x, y, _w) = self.graph.edges[delta_edge] (x, y, _w) = self.graph.edges[delta_edge]
if self.vertex_set_node[x].find().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_s_to_t(x, y)
elif delta_type == 3: elif delta_type == 3:
# Use the S-to-S edge that got unlocked by the delta update. # Use the S-to-S edge that got unlocked by the delta update.
# This reveals either a new blossom or an augmenting path. # This reveals either a new blossom or an augmenting path.
(x, y, _w) = self.graph.edges[delta_edge] (x, y, _w) = self.graph.edges[delta_edge]
augmenting_path = self.add_s_to_s_edge(x, y) if self.add_s_to_s_edge(x, y):
if augmenting_path is not None: # Matching was augmented. End the stage.
# Found augmenting path.
# Delete the two alternating trees on the augmenting path.
bx = self.vertex_set_node[x].find()
by = self.vertex_set_node[y].find()
assert bx.tree_blossoms is not None
assert by.tree_blossoms is not None
self.remove_alternating_tree(bx.tree_blossoms)
self.remove_alternating_tree(by.tree_blossoms)
# Augment the matching.
self.augment_matching(augmenting_path)
# End the stage.
return True return True
elif delta_type == 4: elif delta_type == 4: