mirror of
https://github.com/TheAlgorithms/Python.git
synced 2025-02-25 18:38:39 +00:00
268 lines
10 KiB
Python
268 lines
10 KiB
Python
from collections import deque
|
|
|
|
|
|
class BlossomAuxData:
|
|
"""Class to hold auxiliary data during the blossom algorithm's execution."""
|
|
|
|
def __init__(
|
|
self,
|
|
queue: deque,
|
|
parent: list[int],
|
|
base: list[int],
|
|
in_blossom: list[bool],
|
|
match: list[int],
|
|
in_queue: list[bool],
|
|
) -> None:
|
|
"""
|
|
Initializes the BlossomAuxData instance.
|
|
|
|
Args:
|
|
queue: A deque for BFS processing.
|
|
parent: List of parent vertices in the augmenting path.
|
|
base: List of base vertices for each vertex.
|
|
in_blossom: Boolean list indicating if a vertex is in a blossom.
|
|
match: List of matched vertices.
|
|
in_queue: Boolean list indicating if a vertex is in the queue.
|
|
"""
|
|
self.queue = queue
|
|
self.parent = parent
|
|
self.base = base
|
|
self.in_blossom = in_blossom
|
|
self.match = match
|
|
self.in_queue = in_queue
|
|
|
|
|
|
class BlossomData:
|
|
"""Class to encapsulate data related to a blossom in the graph."""
|
|
|
|
def __init__(
|
|
self,
|
|
aux_data: BlossomAuxData,
|
|
vertex_u: int,
|
|
vertex_v: int,
|
|
lowest_common_ancestor: int,
|
|
) -> None:
|
|
"""
|
|
Initializes the BlossomData instance.
|
|
|
|
Args:
|
|
aux_data: The auxiliary data related to the blossom.
|
|
vertex_u: One vertex in the blossom.
|
|
vertex_v: The other vertex in the blossom.
|
|
lowest_common_ancestor: The lowest common ancestor of vertex_u and vertex_v.
|
|
"""
|
|
self.aux_data = aux_data
|
|
self.vertex_u = vertex_u
|
|
self.vertex_v = vertex_v
|
|
self.lowest_common_ancestor = lowest_common_ancestor
|
|
|
|
|
|
class EdmondsBlossomAlgorithm:
|
|
UNMATCHED = -1 # Constant to represent unmatched vertices
|
|
|
|
@staticmethod
|
|
def maximum_matching(edges: list[list[int]], vertex_count: int) -> list[list[int]]:
|
|
"""
|
|
Finds the maximum matching in a graph using the Edmonds Blossom Algorithm.
|
|
|
|
Args:
|
|
edges: A list of edges represented as pairs of vertices.
|
|
vertex_count: The total number of vertices in the graph.
|
|
|
|
Returns:
|
|
A list of matched pairs in the form of a list of lists.
|
|
"""
|
|
# Create an adjacency list for the graph
|
|
graph: list[list[int]] = [[] for _ in range(vertex_count)]
|
|
|
|
# Populate the graph with the edges
|
|
for edge in edges:
|
|
u, v = edge
|
|
graph[u].append(v)
|
|
graph[v].append(u)
|
|
|
|
# All vertices are initially unmatched
|
|
match: list[int] = [EdmondsBlossomAlgorithm.UNMATCHED] * vertex_count
|
|
parent: list[int] = [EdmondsBlossomAlgorithm.UNMATCHED] * vertex_count
|
|
# Each vertex is its own base initially
|
|
base: list[int] = list(range(vertex_count))
|
|
in_blossom: list[bool] = [False] * vertex_count
|
|
# Tracks vertices in the BFS queue
|
|
in_queue: list[bool] = [False] * vertex_count
|
|
|
|
# Main logic for finding maximum matching
|
|
for u in range(vertex_count):
|
|
# Only consider unmatched vertices
|
|
if match[u] == EdmondsBlossomAlgorithm.UNMATCHED:
|
|
# BFS initialization
|
|
parent = [EdmondsBlossomAlgorithm.UNMATCHED] * vertex_count
|
|
base = list(range(vertex_count))
|
|
in_blossom = [False] * vertex_count
|
|
in_queue = [False] * vertex_count
|
|
|
|
queue = deque([u]) # Start BFS from the unmatched vertex
|
|
in_queue[u] = True
|
|
|
|
augmenting_path_found = False
|
|
|
|
# BFS to find augmenting paths
|
|
while queue and not augmenting_path_found:
|
|
current = queue.popleft() # Get the current vertex
|
|
for y in graph[current]: # Explore adjacent vertices
|
|
# Skip if we're looking at the current match
|
|
if match[current] == y:
|
|
continue
|
|
|
|
if base[current] == base[y]: # Avoid self-loops
|
|
continue
|
|
|
|
if parent[y] == EdmondsBlossomAlgorithm.UNMATCHED:
|
|
# Case 1: y is unmatched;
|
|
# we've found an augmenting path
|
|
if match[y] == EdmondsBlossomAlgorithm.UNMATCHED:
|
|
parent[y] = current # Update the parent
|
|
augmenting_path_found = True
|
|
# Augment along this path
|
|
EdmondsBlossomAlgorithm.update_matching(
|
|
match, parent, y
|
|
)
|
|
break
|
|
|
|
# Case 2: y is matched;
|
|
# add y's match to the queue
|
|
z = match[y]
|
|
parent[y] = current
|
|
parent[z] = y
|
|
if not in_queue[z]: # If z is not already in the queue
|
|
queue.append(z)
|
|
in_queue[z] = True
|
|
else:
|
|
# Case 3: Both current and y have a parent;
|
|
# check for a cycle/blossom
|
|
base_u = EdmondsBlossomAlgorithm.find_base(
|
|
base, parent, current, y
|
|
)
|
|
if base_u != EdmondsBlossomAlgorithm.UNMATCHED:
|
|
EdmondsBlossomAlgorithm.contract_blossom(
|
|
BlossomData(
|
|
BlossomAuxData(
|
|
queue,
|
|
parent,
|
|
base,
|
|
in_blossom,
|
|
match,
|
|
in_queue,
|
|
),
|
|
current,
|
|
y,
|
|
base_u,
|
|
)
|
|
)
|
|
|
|
# Create result list of matched pairs
|
|
matching_result: list[list[int]] = []
|
|
for v in range(vertex_count):
|
|
if (
|
|
match[v] != EdmondsBlossomAlgorithm.UNMATCHED and v < match[v]
|
|
): # Ensure pairs are unique
|
|
matching_result.append([v, match[v]])
|
|
|
|
return matching_result
|
|
|
|
@staticmethod
|
|
def update_matching(
|
|
match: list[int], parent: list[int], matched_vertex: int
|
|
) -> None:
|
|
"""
|
|
Updates the matching based on the augmenting path found.
|
|
|
|
Args:
|
|
match: The current match list.
|
|
parent: The parent list from BFS traversal.
|
|
matched_vertex: The vertex where the augmenting path ends.
|
|
"""
|
|
while matched_vertex != EdmondsBlossomAlgorithm.UNMATCHED:
|
|
v = parent[matched_vertex] # Get the parent vertex
|
|
next_match = match[v] # Store the next match
|
|
match[v] = matched_vertex # Update match for v
|
|
match[matched_vertex] = v # Update match for matched_vertex
|
|
matched_vertex = next_match # Move to the next vertex
|
|
|
|
@staticmethod
|
|
def find_base(
|
|
base: list[int], parent: list[int], vertex_u: int, vertex_v: int
|
|
) -> int:
|
|
"""
|
|
Finds the base of the blossom.
|
|
|
|
Args:
|
|
base: The base array for each vertex.
|
|
parent: The parent array from BFS.
|
|
vertex_u: One endpoint of the blossom.
|
|
vertex_v: The other endpoint of the blossom.
|
|
|
|
Returns:
|
|
The lowest common ancestor of vertex_u and vertex_v in the blossom.
|
|
"""
|
|
visited: list[bool] = [False] * len(base)
|
|
|
|
# Mark ancestors of vertex_u
|
|
current_vertex_u = vertex_u
|
|
while True:
|
|
current_vertex_u = base[current_vertex_u]
|
|
# Mark this base as visited
|
|
visited[current_vertex_u] = True
|
|
if parent[current_vertex_u] == EdmondsBlossomAlgorithm.UNMATCHED:
|
|
break
|
|
current_vertex_u = parent[current_vertex_u]
|
|
|
|
# Find the common ancestor of vertex_v
|
|
current_vertex_v = vertex_v
|
|
while True:
|
|
current_vertex_v = base[current_vertex_v]
|
|
# Check if we've already visited this base
|
|
if visited[current_vertex_v]:
|
|
return current_vertex_v
|
|
current_vertex_v = parent[current_vertex_v]
|
|
|
|
@staticmethod
|
|
def contract_blossom(blossom_data: BlossomData) -> None:
|
|
"""
|
|
Contracts a blossom found during the matching process.
|
|
|
|
Args:
|
|
blossom_data: The data related to the blossom to be contracted.
|
|
"""
|
|
# Mark vertices in the blossom
|
|
for x in range(
|
|
blossom_data.vertex_u,
|
|
blossom_data.aux_data.base[blossom_data.vertex_u]
|
|
!= blossom_data.lowest_common_ancestor,
|
|
):
|
|
base_x = blossom_data.aux_data.base[x]
|
|
match_base_x = blossom_data.aux_data.base[blossom_data.aux_data.match[x]]
|
|
# Mark the base as in a blossom
|
|
blossom_data.aux_data.in_blossom[base_x] = True
|
|
blossom_data.aux_data.in_blossom[match_base_x] = True
|
|
|
|
for x in range(
|
|
blossom_data.vertex_v,
|
|
blossom_data.aux_data.base[blossom_data.vertex_v]
|
|
!= blossom_data.lowest_common_ancestor,
|
|
):
|
|
base_x = blossom_data.aux_data.base[x]
|
|
match_base_x = blossom_data.aux_data.base[blossom_data.aux_data.match[x]]
|
|
# Mark the base as in a blossom
|
|
blossom_data.aux_data.in_blossom[base_x] = True
|
|
blossom_data.aux_data.in_blossom[match_base_x] = True
|
|
|
|
# Update the base for all marked vertices
|
|
for i in range(len(blossom_data.aux_data.base)):
|
|
if blossom_data.aux_data.in_blossom[blossom_data.aux_data.base[i]]:
|
|
# Contract to the lowest common ancestor
|
|
blossom_data.aux_data.base[i] = blossom_data.lowest_common_ancestor
|
|
if not blossom_data.aux_data.in_queue[i]:
|
|
# Add to queue if not already present
|
|
blossom_data.aux_data.queue.append(i)
|
|
blossom_data.aux_data.in_queue[i] = True
|