Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 11a3c24

Browse files
committedOct 14, 2024
Resolved Pre commit errors
1 parent 991a37e commit 11a3c24

File tree

1 file changed

+74
-29
lines changed

1 file changed

+74
-29
lines changed
 

‎graphs/edmonds_blossom_algorithm.py

Lines changed: 74 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33

44
class BlossomAuxData:
5+
"""Class to hold auxiliary data during the blossom algorithm's execution."""
6+
57
def __init__(self, queue: deque, parent: list[int], base: list[int],
68
in_blossom: list[bool], match: list[int], in_queue: list[bool]):
79
self.queue = queue
@@ -11,18 +13,33 @@ def __init__(self, queue: deque, parent: list[int], base: list[int],
1113
self.match = match
1214
self.in_queue = in_queue
1315

16+
1417
class BlossomData:
18+
"""Class to encapsulate data related to a blossom in the graph."""
19+
1520
def __init__(self, aux_data: BlossomAuxData, u: int, v: int, lca: int):
1621
self.aux_data = aux_data
1722
self.u = u
1823
self.v = v
1924
self.lca = lca
2025

26+
2127
class EdmondsBlossomAlgorithm:
2228
UNMATCHED = -1 # Constant to represent unmatched vertices
2329

2430
@staticmethod
2531
def maximum_matching(edges: list[list[int]], vertex_count: int) -> list[list[int]]:
32+
"""
33+
Finds the maximum matching in a graph using the Edmonds Blossom Algorithm.
34+
35+
Args:
36+
edges: A list of edges represented as pairs of vertices.
37+
vertex_count: The total number of vertices in the graph.
38+
39+
Returns:
40+
A list of matched pairs in the form of a list of lists.
41+
"""
42+
# Create an adjacency list for the graph
2643
graph = [[] for _ in range(vertex_count)]
2744

2845
# Populate the graph with the edges
@@ -35,95 +52,114 @@ def maximum_matching(edges: list[list[int]], vertex_count: int) -> list[list[int
3552
match = [EdmondsBlossomAlgorithm.UNMATCHED] * vertex_count
3653
parent = [EdmondsBlossomAlgorithm.UNMATCHED] * vertex_count
3754
base = list(range(vertex_count)) # Each vertex is its own base initially
38-
# Indicates if a vertex is part of a blossom
3955
in_blossom = [False] * vertex_count
4056
in_queue = [False] * vertex_count # Tracks vertices in the BFS queue
4157

4258
# Main logic for finding maximum matching
4359
for u in range(vertex_count):
60+
# Only consider unmatched vertices
4461
if match[u] == EdmondsBlossomAlgorithm.UNMATCHED:
4562
# BFS initialization
4663
parent = [EdmondsBlossomAlgorithm.UNMATCHED] * vertex_count
4764
base = list(range(vertex_count))
4865
in_blossom = [False] * vertex_count
4966
in_queue = [False] * vertex_count
5067

51-
queue = deque([u])
68+
queue = deque([u]) # Start BFS from the unmatched vertex
5269
in_queue[u] = True
5370

5471
augmenting_path_found = False
5572

5673
# BFS to find augmenting paths
5774
while queue and not augmenting_path_found:
58-
current = queue.popleft()
59-
for y in graph[current]:
75+
current = queue.popleft() # Get the current vertex
76+
for y in graph[current]: # Explore adjacent vertices
77+
# Skip if we're looking at the current match
6078
if match[current] == y:
61-
# Skip if we are
62-
# looking at the same edge
63-
# as the current match
6479
continue
6580

66-
if base[current] == base[y]:
67-
continue # Avoid self-loops
81+
if base[current] == base[y]: # Avoid self-loops
82+
continue
6883

6984
if parent[y] == EdmondsBlossomAlgorithm.UNMATCHED:
70-
# Case 1: y is unmatched, we've found an augmenting path
85+
# Case 1: y is unmatched; we've found an augmenting path
7186
if match[y] == EdmondsBlossomAlgorithm.UNMATCHED:
72-
parent[y] = current
87+
parent[y] = current # Update the parent
7388
augmenting_path_found = True
7489
# Augment along this path
75-
(EdmondsBlossomAlgorithm
76-
.update_matching(match, parent, y))
90+
EdmondsBlossomAlgorithm.update_matching(match,
91+
parent,
92+
y)
7793
break
7894

79-
# Case 2: y is matched, add y's match to the queue
95+
# Case 2: y is matched; add y's match to the queue
8096
z = match[y]
8197
parent[y] = current
8298
parent[z] = y
83-
if not in_queue[z]:
99+
if not in_queue[z]: # If z is not already in the queue
84100
queue.append(z)
85101
in_queue[z] = True
86102
else:
87103
# Case 3: Both current and y have a parent;
88104
# check for a cycle/blossom
89105
base_u = EdmondsBlossomAlgorithm.find_base(base,
90-
parent, current, y)
106+
parent,
107+
current,
108+
y)
91109
if base_u != EdmondsBlossomAlgorithm.UNMATCHED:
92110
EdmondsBlossomAlgorithm.contract_blossom(BlossomData(
93-
BlossomAuxData(queue,
94-
parent,
95-
base,
96-
in_blossom,
97-
match,
98-
in_queue),
111+
BlossomAuxData(queue, parent,
112+
base, in_blossom,
113+
match, in_queue),
99114
current, y, base_u))
100115

101116
# Create result list of matched pairs
102117
matching_result = []
103118
for v in range(vertex_count):
119+
# Ensure pairs are unique
104120
if match[v] != EdmondsBlossomAlgorithm.UNMATCHED and v < match[v]:
105121
matching_result.append([v, match[v]])
106122

107123
return matching_result
108124

109125
@staticmethod
110126
def update_matching(match: list[int], parent: list[int], u: int):
127+
"""
128+
Updates the matching based on the augmenting path found.
129+
130+
Args:
131+
match: The current match list.
132+
parent: The parent list from BFS traversal.
133+
u: The vertex where the augmenting path ends.
134+
"""
111135
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
136+
v = parent[u] # Get the parent vertex
137+
next_match = match[v] # Store the next match
138+
match[v] = u # Update match for v
139+
match[u] = v # Update match for u
140+
u = next_match # Move to the next vertex
117141

118142
@staticmethod
119143
def find_base(base: list[int], parent: list[int], u: int, v: int) -> int:
144+
"""
145+
Finds the base of the blossom.
146+
147+
Args:
148+
base: The base array for each vertex.
149+
parent: The parent array from BFS.
150+
u: One endpoint of the blossom.
151+
v: The other endpoint of the blossom.
152+
153+
Returns:
154+
The lowest common ancestor of u and v in the blossom.
155+
"""
120156
visited = [False] * len(base)
121157

122158
# Mark ancestors of u
123159
current_u = u
124160
while True:
125161
current_u = base[current_u]
126-
visited[current_u] = True
162+
visited[current_u] = True # Mark this base as visited
127163
if parent[current_u] == EdmondsBlossomAlgorithm.UNMATCHED:
128164
break
129165
current_u = parent[current_u]
@@ -132,23 +168,32 @@ def find_base(base: list[int], parent: list[int], u: int, v: int) -> int:
132168
current_v = v
133169
while True:
134170
current_v = base[current_v]
135-
if visited[current_v]:
171+
if visited[current_v]: # Check if we've already visited this base
136172
return current_v
137173
current_v = parent[current_v]
138174

139175
@staticmethod
140176
def contract_blossom(blossom_data: BlossomData):
177+
"""
178+
Contracts a blossom found during the matching process.
179+
180+
Args:
181+
blossom_data: The data related to the blossom to be contracted.
182+
"""
183+
# Mark vertices in the blossom
141184
for x in range(blossom_data.u,
142185
blossom_data.aux_data.base[blossom_data.u] != blossom_data.lca):
143186
base_x = blossom_data.aux_data.base[x]
144187
match_base_x = blossom_data.aux_data.base[blossom_data.aux_data.match[x]]
188+
# Mark the base as in a blossom
145189
blossom_data.aux_data.in_blossom[base_x] = True
146190
blossom_data.aux_data.in_blossom[match_base_x] = True
147191

148192
for x in range(blossom_data.v,
149193
blossom_data.aux_data.base[blossom_data.v] != blossom_data.lca):
150194
base_x = blossom_data.aux_data.base[x]
151195
match_base_x = blossom_data.aux_data.base[blossom_data.aux_data.match[x]]
196+
# Mark the base as in a blossom
152197
blossom_data.aux_data.in_blossom[base_x] = True
153198
blossom_data.aux_data.in_blossom[match_base_x] = True
154199

0 commit comments

Comments
 (0)
Please sign in to comment.