Clean up management of the alternating tree
This commit is contained in:
parent
b960a85b6c
commit
f35a640e43
|
@ -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)
|
||||||
|
|
||||||
y = self.vertex_mate[x]
|
|
||||||
if 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.
|
|
||||||
bx.tree_edge = None
|
|
||||||
bx.tree_blossoms = {bx}
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Vertex "x" is matched to T-vertex "y".
|
# Vertex "x" is matched to T-vertex "y".
|
||||||
|
y = self.vertex_mate[x]
|
||||||
|
assert y != -1
|
||||||
|
|
||||||
by = self.vertex_set_node[y].find()
|
by = self.vertex_set_node[y].find()
|
||||||
assert by.label == _LABEL_T
|
assert by.label == _LABEL_T
|
||||||
|
assert by.tree_blossoms is not None
|
||||||
|
|
||||||
# Attach the blossom that contains "x" to the alternating tree.
|
# Attach the blossom that contains "x" to the alternating tree.
|
||||||
bx.tree_edge = (y, x)
|
bx.tree_edge = (y, x)
|
||||||
|
|
||||||
bx.tree_blossoms = by.tree_blossoms
|
bx.tree_blossoms = by.tree_blossoms
|
||||||
assert bx.tree_blossoms is not None
|
|
||||||
bx.tree_blossoms.add(bx)
|
bx.tree_blossoms.add(bx)
|
||||||
|
|
||||||
def extend_tree_t(self, x: int, y: int) -> None:
|
def extend_tree_s_to_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:
|
||||||
|
|
Loading…
Reference in New Issue