mirror of
https://github.com/TheAlgorithms/Python.git
synced 2024-12-04 02:11:08 +00:00
Added edmonds_blossom_algorithm.py. For maximum matching in the graph.
This commit is contained in:
parent
e9e7c96465
commit
e0bc96940e
236
graphs/edmonds_blossom_algorithm.py
Normal file
236
graphs/edmonds_blossom_algorithm.py
Normal file
|
@ -0,0 +1,236 @@
|
|||
from collections import defaultdict, deque
|
||||
|
||||
UNMATCHED = -1 # Constant to represent unmatched vertices
|
||||
|
||||
|
||||
class EdmondsBlossomAlgorithm:
|
||||
@staticmethod
|
||||
def maximum_matching(edges: list[tuple[int, int]], vertex_count: int) \
|
||||
-> list[tuple[int, int]]:
|
||||
"""
|
||||
Finds the maximum matching in a general graph using Edmonds' Blossom Algorithm.
|
||||
|
||||
:param edges: List of edges in the graph.
|
||||
:param vertex_count: Number of vertices in the graph.
|
||||
:return: A list of matched pairs of vertices.
|
||||
|
||||
>>> EdmondsBlossomAlgorithm.maximum_matching([(0, 1), (1, 2), (2, 3)], 4)
|
||||
[(0, 1), (2, 3)]
|
||||
"""
|
||||
graph: dict[int, list[int]] = defaultdict(list)
|
||||
|
||||
# Populate the graph with the edges
|
||||
for vertex_u, vertex_v in edges:
|
||||
graph[vertex_u].append(vertex_v)
|
||||
graph[vertex_v].append(vertex_u)
|
||||
|
||||
# Initial matching array and auxiliary data structures
|
||||
match = [UNMATCHED] * vertex_count
|
||||
parent = [UNMATCHED] * vertex_count
|
||||
base = list(range(vertex_count))
|
||||
in_blossom = [False] * vertex_count
|
||||
in_queue = [False] * vertex_count
|
||||
|
||||
# Main logic for finding maximum matching
|
||||
for vertex_u in range(vertex_count):
|
||||
if match[vertex_u] == UNMATCHED:
|
||||
# BFS initialization
|
||||
parent = [UNMATCHED] * vertex_count
|
||||
base = list(range(vertex_count))
|
||||
in_blossom = [False] * vertex_count
|
||||
in_queue = [False] * vertex_count
|
||||
|
||||
queue = deque([vertex_u])
|
||||
in_queue[vertex_u] = True
|
||||
|
||||
augmenting_path_found = False
|
||||
|
||||
# BFS to find augmenting paths
|
||||
while queue and not augmenting_path_found:
|
||||
current_vertex = queue.popleft()
|
||||
for neighbor in graph[current_vertex]:
|
||||
if match[current_vertex] == neighbor:
|
||||
continue
|
||||
|
||||
if base[current_vertex] == base[neighbor]:
|
||||
continue # Avoid self-loops
|
||||
|
||||
if parent[neighbor] == UNMATCHED:
|
||||
# Case 1: neighbor is unmatched,
|
||||
# we've found an augmenting path
|
||||
if match[neighbor] == UNMATCHED:
|
||||
parent[neighbor] = current_vertex
|
||||
augmenting_path_found = True
|
||||
EdmondsBlossomAlgorithm.update_matching(
|
||||
match, parent, neighbor
|
||||
)
|
||||
break
|
||||
|
||||
# Case 2: neighbor is matched,
|
||||
# add neighbor's match to the queue
|
||||
matched_vertex = match[neighbor]
|
||||
parent[neighbor] = current_vertex
|
||||
parent[matched_vertex] = neighbor
|
||||
if not in_queue[matched_vertex]:
|
||||
queue.append(matched_vertex)
|
||||
in_queue[matched_vertex] = True
|
||||
else:
|
||||
# Case 3: Both current_vertex and neighbor have a parent;
|
||||
# check for a cycle/blossom
|
||||
base_vertex = EdmondsBlossomAlgorithm.find_base(
|
||||
base, parent, current_vertex, neighbor
|
||||
)
|
||||
if base_vertex != UNMATCHED:
|
||||
EdmondsBlossomAlgorithm.contract_blossom(
|
||||
BlossomData(
|
||||
BlossomAuxData(
|
||||
queue, parent, base, in_blossom,
|
||||
match, in_queue
|
||||
),
|
||||
current_vertex, neighbor, base_vertex
|
||||
)
|
||||
)
|
||||
|
||||
# Create result list of matched pairs
|
||||
matching_result = []
|
||||
for vertex in range(vertex_count):
|
||||
if match[vertex] != UNMATCHED and vertex < match[vertex]:
|
||||
matching_result.append((vertex, match[vertex]))
|
||||
|
||||
return matching_result
|
||||
|
||||
@staticmethod
|
||||
def update_matching(match: list[int],
|
||||
parent: list[int], current_vertex: int) -> None:
|
||||
"""
|
||||
Updates the matching along the augmenting path found.
|
||||
|
||||
:param match: The matching array.
|
||||
:param parent: The parent array used during the BFS.
|
||||
:param current_vertex: The starting node of the augmenting path.
|
||||
|
||||
>>> match = [UNMATCHED, UNMATCHED, UNMATCHED]
|
||||
>>> parent = [1, 0, UNMATCHED]
|
||||
>>> EdmondsBlossomAlgorithm.update_matching(match, parent, 2)
|
||||
>>> match
|
||||
[1, 0, -1]
|
||||
"""
|
||||
while current_vertex != UNMATCHED:
|
||||
matched_vertex = parent[current_vertex]
|
||||
next_vertex = match[matched_vertex]
|
||||
match[matched_vertex] = current_vertex
|
||||
match[current_vertex] = matched_vertex
|
||||
current_vertex = next_vertex
|
||||
|
||||
@staticmethod
|
||||
def find_base(
|
||||
base: list[int], parent: list[int], vertex_u: int, vertex_v: int
|
||||
) -> int:
|
||||
"""
|
||||
Finds the base of a node in the blossom.
|
||||
|
||||
:param base: The base array.
|
||||
:param parent: The parent array.
|
||||
:param vertex_u: One end of the edge.
|
||||
:param vertex_v: The other end of the edge.
|
||||
:return: The base of the node or UNMATCHED.
|
||||
|
||||
>>> base = [0, 1, 2, 3]
|
||||
>>> parent = [1, 0, UNMATCHED, UNMATCHED]
|
||||
>>> EdmondsBlossomAlgorithm.find_base(base, parent, 2, 3)
|
||||
2
|
||||
"""
|
||||
visited = [False] * len(base)
|
||||
|
||||
# Mark ancestors of vertex_u
|
||||
current_vertex_u = vertex_u
|
||||
while True:
|
||||
current_vertex_u = base[current_vertex_u]
|
||||
visited[current_vertex_u] = True
|
||||
if parent[current_vertex_u] == 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]
|
||||
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 in the graph, modifying the base array
|
||||
and marking the vertices involved.
|
||||
|
||||
:param blossom_data: An object containing the necessary data
|
||||
to perform the contraction.
|
||||
|
||||
>>> aux_data = BlossomAuxData(deque(), [], [], [], [], [])
|
||||
>>> blossom_data = BlossomData(aux_data, 0, 1, 2)
|
||||
>>> EdmondsBlossomAlgorithm.contract_blossom(blossom_data)
|
||||
"""
|
||||
# Mark all vertices in the blossom
|
||||
current_vertex_u = blossom_data.u
|
||||
while blossom_data.aux_data.base[current_vertex_u] != blossom_data.lca:
|
||||
base_u = blossom_data.aux_data.base[current_vertex_u]
|
||||
match_base_u = blossom_data.aux_data.base[blossom_data.aux_data.match
|
||||
[current_vertex_u]
|
||||
]
|
||||
blossom_data.aux_data.in_blossom[base_u] = True
|
||||
blossom_data.aux_data.in_blossom[match_base_u] = True
|
||||
current_vertex_u = blossom_data.aux_data.parent[
|
||||
blossom_data.aux_data.match[current_vertex_u]
|
||||
]
|
||||
|
||||
current_vertex_v = blossom_data.v
|
||||
while blossom_data.aux_data.base[current_vertex_v] != blossom_data.lca:
|
||||
base_v = blossom_data.aux_data.base[current_vertex_v]
|
||||
match_base_v = blossom_data.aux_data.base[blossom_data.aux_data.match
|
||||
[current_vertex_v]
|
||||
]
|
||||
blossom_data.aux_data.in_blossom[base_v] = True
|
||||
blossom_data.aux_data.in_blossom[match_base_v] = True
|
||||
current_vertex_v = blossom_data.aux_data.parent[
|
||||
blossom_data.aux_data.match[current_vertex_v]
|
||||
]
|
||||
|
||||
# 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]]:
|
||||
blossom_data.aux_data.base[i] = blossom_data.lca
|
||||
if not blossom_data.aux_data.in_queue[i]:
|
||||
blossom_data.aux_data.queue.append(i)
|
||||
blossom_data.aux_data.in_queue[i] = True
|
||||
|
||||
|
||||
class BlossomAuxData:
|
||||
"""
|
||||
Auxiliary data class to encapsulate common parameters for the blossom operations.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, queue: deque, parent: list[int], base: list[int], in_blossom: list[bool],
|
||||
match: list[int], in_queue: list[bool]
|
||||
) -> None:
|
||||
self.queue = queue
|
||||
self.parent = parent
|
||||
self.base = base
|
||||
self.in_blossom = in_blossom
|
||||
self.match = match
|
||||
self.in_queue = in_queue
|
||||
|
||||
|
||||
class BlossomData:
|
||||
"""
|
||||
BlossomData class with reduced parameters.
|
||||
"""
|
||||
|
||||
def __init__(self, aux_data: BlossomAuxData, u: int, v: int, lca: int) -> None:
|
||||
self.aux_data = aux_data
|
||||
self.u = u
|
||||
self.v = v
|
||||
self.lca = lca
|
||||
|
Loading…
Reference in New Issue
Block a user