Skip to content

Commit 991a37e

Browse files
Changes in the main file and test file as test were failing due to stuck in an infinite loop.
1 parent 46dd5fd commit 991a37e

File tree

2 files changed

+180
-274
lines changed

2 files changed

+180
-274
lines changed

graphs/edmonds_blossom_algorithm.py

+109-205
Original file line numberDiff line numberDiff line change
@@ -1,259 +1,163 @@
1-
from collections import defaultdict, deque
1+
from collections import deque
22

3-
UNMATCHED = -1 # Constant to represent unmatched vertices
43

4+
class BlossomAuxData:
5+
def __init__(self, queue: deque, parent: list[int], base: list[int],
6+
in_blossom: list[bool], match: list[int], in_queue: list[bool]):
7+
self.queue = queue
8+
self.parent = parent
9+
self.base = base
10+
self.in_blossom = in_blossom
11+
self.match = match
12+
self.in_queue = in_queue
513

6-
class EdmondsBlossomAlgorithm:
7-
@staticmethod
8-
def maximum_matching(
9-
edges: list[tuple[int, int]], vertex_count: int
10-
) -> list[tuple[int, int]]:
11-
"""
12-
Finds the maximum matching in a general graph using Edmonds' Blossom Algorithm.
14+
class BlossomData:
15+
def __init__(self, aux_data: BlossomAuxData, u: int, v: int, lca: int):
16+
self.aux_data = aux_data
17+
self.u = u
18+
self.v = v
19+
self.lca = lca
1320

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.
21+
class EdmondsBlossomAlgorithm:
22+
UNMATCHED = -1 # Constant to represent unmatched vertices
1723

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)
24+
@staticmethod
25+
def maximum_matching(edges: list[list[int]], vertex_count: int) -> list[list[int]]:
26+
graph = [[] for _ in range(vertex_count)]
2227

2328
# 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))
29+
for edge in edges:
30+
u, v = edge
31+
graph[u].append(v)
32+
graph[v].append(u)
33+
34+
# All vertices are initially unmatched
35+
match = [EdmondsBlossomAlgorithm.UNMATCHED] * vertex_count
36+
parent = [EdmondsBlossomAlgorithm.UNMATCHED] * vertex_count
37+
base = list(range(vertex_count)) # Each vertex is its own base initially
38+
# Indicates if a vertex is part of a blossom
3239
in_blossom = [False] * vertex_count
33-
in_queue = [False] * vertex_count
40+
in_queue = [False] * vertex_count # Tracks vertices in the BFS queue
3441

3542
# Main logic for finding maximum matching
36-
for vertex_u in range(vertex_count):
37-
if match[vertex_u] == UNMATCHED:
43+
for u in range(vertex_count):
44+
if match[u] == EdmondsBlossomAlgorithm.UNMATCHED:
3845
# BFS initialization
39-
parent = [UNMATCHED] * vertex_count
46+
parent = [EdmondsBlossomAlgorithm.UNMATCHED] * vertex_count
4047
base = list(range(vertex_count))
4148
in_blossom = [False] * vertex_count
4249
in_queue = [False] * vertex_count
4350

44-
queue = deque([vertex_u])
45-
in_queue[vertex_u] = True
51+
queue = deque([u])
52+
in_queue[u] = True
4653

4754
augmenting_path_found = False
4855

4956
# BFS to find augmenting paths
5057
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:
58+
current = queue.popleft()
59+
for y in graph[current]:
60+
if match[current] == y:
61+
# Skip if we are
62+
# looking at the same edge
63+
# as the current match
5464
continue
5565

56-
if base[current_vertex] == base[neighbor]:
66+
if base[current] == base[y]:
5767
continue # Avoid self-loops
5868

59-
if parent[neighbor] == UNMATCHED:
60-
# Case 1: neighbor is unmatched,
61-
# we've found an augmenting path
62-
if match[neighbor] == UNMATCHED:
63-
parent[neighbor] = current_vertex
69+
if parent[y] == EdmondsBlossomAlgorithm.UNMATCHED:
70+
# Case 1: y is unmatched, we've found an augmenting path
71+
if match[y] == EdmondsBlossomAlgorithm.UNMATCHED:
72+
parent[y] = current
6473
augmenting_path_found = True
65-
EdmondsBlossomAlgorithm.update_matching(
66-
match, parent, neighbor
67-
)
74+
# Augment along this path
75+
(EdmondsBlossomAlgorithm
76+
.update_matching(match, parent, y))
6877
break
6978

70-
# Case 2: neighbor is matched,
71-
# add neighbor's match to the queue
72-
matched_vertex = match[neighbor]
73-
parent[neighbor] = current_vertex
74-
parent[matched_vertex] = neighbor
75-
if not in_queue[matched_vertex]:
76-
queue.append(matched_vertex)
77-
in_queue[matched_vertex] = True
79+
# Case 2: y is matched, add y's match to the queue
80+
z = match[y]
81+
parent[y] = current
82+
parent[z] = y
83+
if not in_queue[z]:
84+
queue.append(z)
85+
in_queue[z] = True
7886
else:
79-
# Case 3: Both current_vertex and neighbor have a parent;
87+
# Case 3: Both current and y have a parent;
8088
# check for a cycle/blossom
81-
base_vertex = EdmondsBlossomAlgorithm.find_base(
82-
base, parent, current_vertex, neighbor
83-
)
84-
if base_vertex != UNMATCHED:
85-
EdmondsBlossomAlgorithm.contract_blossom(
86-
BlossomData(
87-
BlossomAuxData(
88-
queue,
89-
parent,
90-
base,
91-
in_blossom,
92-
match,
93-
in_queue,
94-
),
95-
current_vertex,
96-
neighbor,
97-
base_vertex,
98-
)
99-
)
89+
base_u = EdmondsBlossomAlgorithm.find_base(base,
90+
parent, current, y)
91+
if base_u != EdmondsBlossomAlgorithm.UNMATCHED:
92+
EdmondsBlossomAlgorithm.contract_blossom(BlossomData(
93+
BlossomAuxData(queue,
94+
parent,
95+
base,
96+
in_blossom,
97+
match,
98+
in_queue),
99+
current, y, base_u))
100100

101101
# Create result list of matched pairs
102102
matching_result = []
103-
for vertex in range(vertex_count):
104-
if match[vertex] != UNMATCHED and vertex < match[vertex]:
105-
matching_result.append((vertex, match[vertex]))
103+
for v in range(vertex_count):
104+
if match[v] != EdmondsBlossomAlgorithm.UNMATCHED and v < match[v]:
105+
matching_result.append([v, match[v]])
106106

107107
return matching_result
108108

109109
@staticmethod
110-
def update_matching(
111-
match: list[int], parent: list[int], current_vertex: int
112-
) -> None:
113-
"""
114-
Updates the matching along the augmenting path found.
115-
116-
:param match: The matching array.
117-
:param parent: The parent array used during the BFS.
118-
:param current_vertex: The starting node of the augmenting path.
119-
120-
>>> match = [UNMATCHED, UNMATCHED, UNMATCHED]
121-
>>> parent = [1, 0, UNMATCHED]
122-
>>> EdmondsBlossomAlgorithm.update_matching(match, parent, 2)
123-
>>> match
124-
[1, 0, -1]
125-
"""
126-
while current_vertex != UNMATCHED:
127-
matched_vertex = parent[current_vertex]
128-
next_vertex = match[matched_vertex]
129-
match[matched_vertex] = current_vertex
130-
match[current_vertex] = matched_vertex
131-
current_vertex = next_vertex
110+
def update_matching(match: list[int], parent: list[int], u: int):
111+
while u != EdmondsBlossomAlgorithm.UNMATCHED:
112+
v = parent[u]
113+
next_match = match[v]
114+
match[v] = u
115+
match[u] = v
116+
u = next_match
132117

133118
@staticmethod
134-
def find_base(
135-
base: list[int], parent: list[int], vertex_u: int, vertex_v: int
136-
) -> int:
137-
"""
138-
Finds the base of a node in the blossom.
139-
140-
:param base: The base array.
141-
:param parent: The parent array.
142-
:param vertex_u: One end of the edge.
143-
:param vertex_v: The other end of the edge.
144-
:return: The base of the node or UNMATCHED.
145-
146-
>>> base = [0, 1, 2, 3]
147-
>>> parent = [1, 0, UNMATCHED, UNMATCHED]
148-
>>> EdmondsBlossomAlgorithm.find_base(base, parent, 2, 3)
149-
2
150-
"""
119+
def find_base(base: list[int], parent: list[int], u: int, v: int) -> int:
151120
visited = [False] * len(base)
152121

153-
# Mark ancestors of vertex_u
154-
current_vertex_u = vertex_u
122+
# Mark ancestors of u
123+
current_u = u
155124
while True:
156-
current_vertex_u = base[current_vertex_u]
157-
visited[current_vertex_u] = True
158-
if parent[current_vertex_u] == UNMATCHED:
125+
current_u = base[current_u]
126+
visited[current_u] = True
127+
if parent[current_u] == EdmondsBlossomAlgorithm.UNMATCHED:
159128
break
160-
current_vertex_u = parent[current_vertex_u]
129+
current_u = parent[current_u]
161130

162-
# Find the common ancestor of vertex_v
163-
current_vertex_v = vertex_v
131+
# Find the common ancestor of v
132+
current_v = v
164133
while True:
165-
current_vertex_v = base[current_vertex_v]
166-
if visited[current_vertex_v]:
167-
return current_vertex_v
168-
current_vertex_v = parent[current_vertex_v]
134+
current_v = base[current_v]
135+
if visited[current_v]:
136+
return current_v
137+
current_v = parent[current_v]
169138

170139
@staticmethod
171-
def contract_blossom(blossom_data: "BlossomData") -> None:
172-
"""
173-
Contracts a blossom in the graph, modifying the base array
174-
and marking the vertices involved.
175-
176-
:param blossom_data: An object containing the necessary data
177-
to perform the contraction.
178-
179-
>>> aux_data = BlossomAuxData(deque(), [], [], [], [], [])
180-
>>> blossom_data = BlossomData(aux_data, 0, 1, 2)
181-
>>> EdmondsBlossomAlgorithm.contract_blossom(blossom_data)
182-
"""
183-
# Mark all vertices in the blossom
184-
current_vertex_u = blossom_data.vertex_u
185-
while blossom_data.aux_data.base[current_vertex_u] != blossom_data.lca:
186-
base_u = blossom_data.aux_data.base[current_vertex_u]
187-
match_base_u = blossom_data.aux_data.base[
188-
blossom_data.aux_data.match[current_vertex_u]
189-
]
190-
blossom_data.aux_data.in_blossom[base_u] = True
191-
blossom_data.aux_data.in_blossom[match_base_u] = True
192-
current_vertex_u = blossom_data.aux_data.parent[
193-
blossom_data.aux_data.match[current_vertex_u]
194-
]
195-
196-
current_vertex_v = blossom_data.vertex_v
197-
while blossom_data.aux_data.base[current_vertex_v] != blossom_data.lca:
198-
base_v = blossom_data.aux_data.base[current_vertex_v]
199-
match_base_v = blossom_data.aux_data.base[
200-
blossom_data.aux_data.match[current_vertex_v]
201-
]
202-
blossom_data.aux_data.in_blossom[base_v] = True
203-
blossom_data.aux_data.in_blossom[match_base_v] = True
204-
current_vertex_v = blossom_data.aux_data.parent[
205-
blossom_data.aux_data.match[current_vertex_v]
206-
]
140+
def contract_blossom(blossom_data: BlossomData):
141+
for x in range(blossom_data.u,
142+
blossom_data.aux_data.base[blossom_data.u] != blossom_data.lca):
143+
base_x = blossom_data.aux_data.base[x]
144+
match_base_x = blossom_data.aux_data.base[blossom_data.aux_data.match[x]]
145+
blossom_data.aux_data.in_blossom[base_x] = True
146+
blossom_data.aux_data.in_blossom[match_base_x] = True
147+
148+
for x in range(blossom_data.v,
149+
blossom_data.aux_data.base[blossom_data.v] != blossom_data.lca):
150+
base_x = blossom_data.aux_data.base[x]
151+
match_base_x = blossom_data.aux_data.base[blossom_data.aux_data.match[x]]
152+
blossom_data.aux_data.in_blossom[base_x] = True
153+
blossom_data.aux_data.in_blossom[match_base_x] = True
207154

208155
# Update the base for all marked vertices
209156
for i in range(len(blossom_data.aux_data.base)):
210157
if blossom_data.aux_data.in_blossom[blossom_data.aux_data.base[i]]:
158+
# Contract to the lowest common ancestor
211159
blossom_data.aux_data.base[i] = blossom_data.lca
212160
if not blossom_data.aux_data.in_queue[i]:
161+
# Add to queue if not already present
213162
blossom_data.aux_data.queue.append(i)
214163
blossom_data.aux_data.in_queue[i] = True
215-
216-
217-
class BlossomAuxData:
218-
"""
219-
Auxiliary data class to encapsulate common parameters for the blossom operations.
220-
"""
221-
222-
def __init__(
223-
self,
224-
queue: deque,
225-
parent: list[int],
226-
base: list[int],
227-
in_blossom: list[bool],
228-
match: list[int],
229-
in_queue: list[bool],
230-
) -> None:
231-
self.queue = queue
232-
self.parent = parent
233-
self.base = base
234-
self.in_blossom = in_blossom
235-
self.match = match
236-
self.in_queue = in_queue
237-
238-
239-
class BlossomData:
240-
"""
241-
BlossomData class with reduced parameters.
242-
"""
243-
244-
def __init__(
245-
self, aux_data: BlossomAuxData, vertex_u: int, vertex_v: int, lca: int
246-
) -> None:
247-
"""
248-
Initialize BlossomData with auxiliary data, two vertices,
249-
and the lowest common ancestor.
250-
251-
:param aux_data: Auxiliary data used in the algorithm
252-
:param vertex_u: First vertex involved in the blossom
253-
:param vertex_v: Second vertex involved in the blossom
254-
:param lca: Lowest common ancestor (base) of the two vertices
255-
"""
256-
self.aux_data = aux_data
257-
self.vertex_u = vertex_u
258-
self.vertex_v = vertex_v
259-
self.lca = lca

0 commit comments

Comments
 (0)