The Hungarian Algorithm is a classic approach to solve the Maximum Bipartite Matching problem. In a bipartite graph, it identifies the largest set of edges such that each vertex is connected to at most one adjacent vertex.
To illustrate its operation, consider a bipartite graph with left vertices (tasks: A, B, C, D) and right vetrices (workers: 1, 2, 3, 4), where matrix entries represent edge weights:
1 2 3 4
A 3 2 0 1
B 2 4 1 0
C 1 3 2 1
D 0 2 5 2
Step 1: Initialization
Left vertices start with a label of 0 (task priorities):
A: 0
B: 0
C: 0
D: 0
Step 2: Finding Augmentinng Paths
We iteratively find paths that increase the match count. Start with vertex A:
- For A: Edges exist to
3and4. First, checkA-3(unmatched) → match A-3. Then checkA-4(unmatched) → match A-4. - For B: Edges to
1and2. CheckB-1(unmatched) → match B-1. - For C: Edge to
3(unmatched) → match C-3. - For D: Edge to
2(unmatched) → match D-2.
Step 3: Result
The maximum matching includes:
A-3, A-4, B-1, C-3, D-2 (all tasks are assigned).
Algorithm Steps (Summary)
- Initialize: Set initial labels for left vertices and track unmatched states.
- Find Augmenting Paths: For each unmatched left vertex, use DFS to find a path to an unmatched right vertex (or a matched vertex with an augmenting path).
- Update Matches: If an augmenting path is found, update the matching and repeat.
- Terminate: When no new augmenting paths exist, the maximum matching is found.
Code Implemantation (with Modified Structure/Variable Names)
class BipartiteMatcher:
def __init__(self, adj_matrix):
self.matrix = adj_matrix
self.row_count = len(adj_matrix)
self.col_count = len(adj_matrix[0]) if self.row_count > 0 else 0
self.matching = [-1] * self.col_count # Tracks right-to-left matches
self.visited_cols = [] # For DFS visit tracking
def find_max_matching(self):
match_count = 0
for row in range(self.row_count):
self.visited_cols = [False] * self.col_count
if self._dfs(row):
match_count += 1
return match_count, self.matching
def _dfs(self, current_row):
for col in range(self.col_count):
if self.matrix[current_row][col] and not self.visited_cols[col]:
self.visited_cols[col] = True
if self.matching[col] == -1 or self._dfs(self.matching[col]):
self.matching[col] = current_row
return True
return False
# Example Usage
if __name__ == "__main__":
# Bipartite graph as adjacency matrix (1 = edge exists)
task_worker_matrix = [
[0, 1, 1, 0], # A: edges to 2, 3
[1, 0, 0, 1], # B: edges to 1, 4
[0, 0, 1, 0], # C: edge to 3
[0, 0, 1, 1] # D: edges to 3, 4
]
matcher = BipartiteMatcher(task_worker_matrix)
total_matches, match_details = matcher.find_max_matching()
print("Maximum Matches:", total_matches)
print("Match Assignment (col → row):", match_details)
In practice, the algorithm adjusts vertex labels and repeats path-finding until no new augmenting paths exist, ensuring the largest possible matching.