Skip to content

Commit 6918d4c

Browse files
Added edmonds_blossom_algorithm.py
1 parent e9e7c96 commit 6918d4c

File tree

1 file changed

+211
-0
lines changed

1 file changed

+211
-0
lines changed

graphs/edmonds_blossom_algorithm.py

+211
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
from collections import deque, defaultdict
2+
from typing import List, Tuple, Dict
3+
4+
5+
UNMATCHED = -1 # Constant to represent unmatched vertices
6+
7+
8+
class EdmondsBlossomAlgorithm:
9+
@staticmethod
10+
def maximum_matching(edges: List[Tuple[int, int]], vertex_count: int) -> List[Tuple[int, int]]:
11+
"""
12+
Finds the maximum matching in a general graph using Edmonds' Blossom Algorithm.
13+
14+
:param edges: List of edges in the graph.
15+
:param vertex_count: Number of vertices in the graph.
16+
:return: A list of matched pairs of vertices.
17+
18+
>>> EdmondsBlossomAlgorithm.maximum_matching([(0, 1), (1, 2), (2, 3)], 4)
19+
[(0, 1), (2, 3)]
20+
"""
21+
graph: Dict[int, List[int]] = defaultdict(list)
22+
23+
# Populate the graph with the edges
24+
for vertex_u, vertex_v in edges:
25+
graph[vertex_u].append(vertex_v)
26+
graph[vertex_v].append(vertex_u)
27+
28+
# Initial matching array and auxiliary data structures
29+
match = [UNMATCHED] * vertex_count
30+
parent = [UNMATCHED] * vertex_count
31+
base = list(range(vertex_count))
32+
in_blossom = [False] * vertex_count
33+
in_queue = [False] * vertex_count
34+
35+
# Main logic for finding maximum matching
36+
for vertex_u in range(vertex_count):
37+
if match[vertex_u] == UNMATCHED:
38+
# BFS initialization
39+
parent = [UNMATCHED] * vertex_count
40+
base = list(range(vertex_count))
41+
in_blossom = [False] * vertex_count
42+
in_queue = [False] * vertex_count
43+
44+
queue = deque([vertex_u])
45+
in_queue[vertex_u] = True
46+
47+
augmenting_path_found = False
48+
49+
# BFS to find augmenting paths
50+
while queue and not augmenting_path_found:
51+
current_vertex = queue.popleft()
52+
for neighbor in graph[current_vertex]:
53+
if match[current_vertex] == neighbor:
54+
continue
55+
56+
if base[current_vertex] == base[neighbor]:
57+
continue # Avoid self-loops
58+
59+
if parent[neighbor] == UNMATCHED:
60+
# Case 1: neighbor is unmatched, we've found an augmenting path
61+
if match[neighbor] == UNMATCHED:
62+
parent[neighbor] = current_vertex
63+
augmenting_path_found = True
64+
EdmondsBlossomAlgorithm.update_matching(match, parent, neighbor)
65+
break
66+
67+
# Case 2: neighbor is matched, add neighbor's match to the queue
68+
matched_vertex = match[neighbor]
69+
parent[neighbor] = current_vertex
70+
parent[matched_vertex] = neighbor
71+
if not in_queue[matched_vertex]:
72+
queue.append(matched_vertex)
73+
in_queue[matched_vertex] = True
74+
else:
75+
# Case 3: Both current_vertex and neighbor have a parent; check for a cycle/blossom
76+
base_vertex = EdmondsBlossomAlgorithm.find_base(base, parent, current_vertex, neighbor)
77+
if base_vertex != UNMATCHED:
78+
EdmondsBlossomAlgorithm.contract_blossom(BlossomData(
79+
BlossomAuxData(queue, parent, base, in_blossom, match, in_queue),
80+
current_vertex, neighbor, base_vertex
81+
))
82+
83+
# Create result list of matched pairs
84+
matching_result = []
85+
for vertex in range(vertex_count):
86+
if match[vertex] != UNMATCHED and vertex < match[vertex]:
87+
matching_result.append((vertex, match[vertex]))
88+
89+
return matching_result
90+
91+
@staticmethod
92+
def update_matching(match: List[int], parent: List[int], current_vertex: int) -> None:
93+
"""
94+
Updates the matching along the augmenting path found.
95+
96+
:param match: The matching array.
97+
:param parent: The parent array used during the BFS.
98+
:param current_vertex: The starting node of the augmenting path.
99+
100+
>>> match = [UNMATCHED, UNMATCHED, UNMATCHED]
101+
>>> parent = [1, 0, UNMATCHED]
102+
>>> EdmondsBlossomAlgorithm.update_matching(match, parent, 2)
103+
>>> match
104+
[1, 0, -1]
105+
"""
106+
while current_vertex != UNMATCHED:
107+
matched_vertex = parent[current_vertex]
108+
next_vertex = match[matched_vertex]
109+
match[matched_vertex] = current_vertex
110+
match[current_vertex] = matched_vertex
111+
current_vertex = next_vertex
112+
113+
@staticmethod
114+
def find_base(base: List[int], parent: List[int], vertex_u: int, vertex_v: int) -> int:
115+
"""
116+
Finds the base of a node in the blossom.
117+
118+
:param base: The base array.
119+
:param parent: The parent array.
120+
:param vertex_u: One end of the edge.
121+
:param vertex_v: The other end of the edge.
122+
:return: The base of the node or UNMATCHED.
123+
124+
>>> base = [0, 1, 2, 3]
125+
>>> parent = [1, 0, UNMATCHED, UNMATCHED]
126+
>>> EdmondsBlossomAlgorithm.find_base(base, parent, 2, 3)
127+
2
128+
"""
129+
visited = [False] * len(base)
130+
131+
# Mark ancestors of vertex_u
132+
current_vertex_u = vertex_u
133+
while True:
134+
current_vertex_u = base[current_vertex_u]
135+
visited[current_vertex_u] = True
136+
if parent[current_vertex_u] == UNMATCHED:
137+
break
138+
current_vertex_u = parent[current_vertex_u]
139+
140+
# Find the common ancestor of vertex_v
141+
current_vertex_v = vertex_v
142+
while True:
143+
current_vertex_v = base[current_vertex_v]
144+
if visited[current_vertex_v]:
145+
return current_vertex_v
146+
current_vertex_v = parent[current_vertex_v]
147+
148+
@staticmethod
149+
def contract_blossom(blossom_data: 'BlossomData') -> None:
150+
"""
151+
Contracts a blossom in the graph, modifying the base array
152+
and marking the vertices involved.
153+
154+
:param blossom_data: An object containing the necessary data
155+
to perform the contraction.
156+
157+
>>> aux_data = BlossomAuxData(deque(), [], [], [], [], [])
158+
>>> blossom_data = BlossomData(aux_data, 0, 1, 2)
159+
>>> EdmondsBlossomAlgorithm.contract_blossom(blossom_data)
160+
"""
161+
# Mark all vertices in the blossom
162+
current_vertex_u = blossom_data.u
163+
while blossom_data.aux_data.base[current_vertex_u] != blossom_data.lca:
164+
base_u = blossom_data.aux_data.base[current_vertex_u]
165+
match_base_u = blossom_data.aux_data.base[blossom_data.aux_data.match[current_vertex_u]]
166+
blossom_data.aux_data.in_blossom[base_u] = True
167+
blossom_data.aux_data.in_blossom[match_base_u] = True
168+
current_vertex_u = blossom_data.aux_data.parent[blossom_data.aux_data.match[current_vertex_u]]
169+
170+
current_vertex_v = blossom_data.v
171+
while blossom_data.aux_data.base[current_vertex_v] != blossom_data.lca:
172+
base_v = blossom_data.aux_data.base[current_vertex_v]
173+
match_base_v = blossom_data.aux_data.base[blossom_data.aux_data.match[current_vertex_v]]
174+
blossom_data.aux_data.in_blossom[base_v] = True
175+
blossom_data.aux_data.in_blossom[match_base_v] = True
176+
current_vertex_v = blossom_data.aux_data.parent[blossom_data.aux_data.match[current_vertex_v]]
177+
178+
# Update the base for all marked vertices
179+
for i in range(len(blossom_data.aux_data.base)):
180+
if blossom_data.aux_data.in_blossom[blossom_data.aux_data.base[i]]:
181+
blossom_data.aux_data.base[i] = blossom_data.lca
182+
if not blossom_data.aux_data.in_queue[i]:
183+
blossom_data.aux_data.queue.append(i)
184+
blossom_data.aux_data.in_queue[i] = True
185+
186+
187+
class BlossomAuxData:
188+
"""
189+
Auxiliary data class to encapsulate common parameters for the blossom operations.
190+
"""
191+
192+
def __init__(self, queue: deque, parent: List[int], base: List[int],
193+
in_blossom: List[bool], match: List[int], in_queue: List[bool]) -> None:
194+
self.queue = queue
195+
self.parent = parent
196+
self.base = base
197+
self.in_blossom = in_blossom
198+
self.match = match
199+
self.in_queue = in_queue
200+
201+
202+
class BlossomData:
203+
"""
204+
BlossomData class with reduced parameters.
205+
"""
206+
207+
def __init__(self, aux_data: BlossomAuxData, u: int, v: int, lca: int) -> None:
208+
self.aux_data = aux_data
209+
self.u = u
210+
self.v = v
211+
self.lca = lca

0 commit comments

Comments
 (0)