Skip to content

Commit 5cfd7d6

Browse files
committed
Add Hopcroft-Karp algorithm and tests
1 parent 57c6b03 commit 5cfd7d6

File tree

3 files changed

+235
-4
lines changed

3 files changed

+235
-4
lines changed

src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
2+
3+
package com.thealgorithms.dynamicprogramming;
4+
5+
import java.util.Arrays;
6+
7+
18
/**
29
* @author Md Asif Joardar
310
*
@@ -14,10 +21,6 @@
1421
* The time complexity of the solution is O(n × sum) and requires O(n × sum) space
1522
*/
1623

17-
package com.thealgorithms.dynamicprogramming;
18-
19-
import java.util.Arrays;
20-
2124
public final class PartitionProblem {
2225
private PartitionProblem() {
2326
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package com.thealgorithms.graph;
2+
3+
import java.util.*;
4+
5+
/**
6+
* @author Panteleimon Tzecheridis
7+
* Implementation of the Hopcroft–Karp algorithm for finding the maximum matching in a bipartite graph.
8+
*
9+
* The bipartite graph is assumed to have:
10+
* - Left part: vertices [0..nLeft-1]
11+
* - Right part: vertices [0..nRight-1]
12+
*
13+
* Adjacency list format: For each left vertex, list the right vertices it is connected to.
14+
* Example:
15+
* adj[0] = [0, 1] // left vertex 0 connects to right vertices 0 and 1
16+
*
17+
* Time complexity: O(E * sqrt(V))
18+
*/
19+
public class HopcroftKarp {
20+
21+
private int nLeft, nRight;
22+
private List<List<Integer>> adj;
23+
private int[] pairU, pairV, dist;
24+
25+
public HopcroftKarp(int nLeft, int nRight, List<List<Integer>> adj) {
26+
this.nLeft = nLeft;
27+
this.nRight = nRight;
28+
this.adj = adj;
29+
this.pairU = new int[nLeft];
30+
this.pairV = new int[nRight];
31+
this.dist = new int[nLeft];
32+
Arrays.fill(pairU, -1);
33+
Arrays.fill(pairV, -1);
34+
}
35+
36+
/**
37+
* Returns the size of the maximum matching.
38+
*/
39+
public int maxMatching() {
40+
int matching = 0;
41+
while (bfs()) {
42+
for (int u = 0; u < nLeft; u++) {
43+
if (pairU[u] == -1 && dfs(u)) {
44+
matching++;
45+
}
46+
}
47+
}
48+
return matching;
49+
}
50+
51+
// BFS to build layers
52+
private boolean bfs() {
53+
Queue<Integer> queue = new ArrayDeque<>();
54+
Arrays.fill(dist, -1);
55+
for (int u = 0; u < nLeft; u++) {
56+
if (pairU[u] == -1) {
57+
dist[u] = 0;
58+
queue.add(u);
59+
}
60+
}
61+
boolean foundAugPath = false;
62+
while (!queue.isEmpty()) {
63+
int u = queue.poll();
64+
for (int v : adj.get(u)) {
65+
int u2 = pairV[v];
66+
if (u2 == -1) {
67+
foundAugPath = true;
68+
} else if (dist[u2] == -1) {
69+
dist[u2] = dist[u] + 1;
70+
queue.add(u2);
71+
}
72+
}
73+
}
74+
return foundAugPath;
75+
}
76+
77+
// DFS to find augmenting paths
78+
private boolean dfs(int u) {
79+
for (int v : adj.get(u)) {
80+
int u2 = pairV[v];
81+
if (u2 == -1 || (dist[u2] == dist[u] + 1 && dfs(u2))) {
82+
pairU[u] = v;
83+
pairV[v] = u;
84+
return true;
85+
}
86+
}
87+
dist[u] = -1;
88+
return false;
89+
}
90+
91+
public int[] getLeftMatches() {
92+
return pairU.clone();
93+
}
94+
95+
public int[] getRightMatches() {
96+
return pairV.clone();
97+
}
98+
99+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package com.thealgorithms.graph;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
5+
import java.util.ArrayList;
6+
import java.util.Arrays;
7+
import java.util.List;
8+
import org.junit.jupiter.api.DisplayName;
9+
import org.junit.jupiter.api.Test;
10+
11+
class HopcroftKarpTest {
12+
13+
private static List<List<Integer>> adj(int nLeft) {
14+
List<List<Integer>> g = new ArrayList<>(nLeft);
15+
for (int i = 0; i < nLeft; i++) g.add(new ArrayList<>());
16+
return g;
17+
}
18+
19+
@Test
20+
@DisplayName("Empty graph has matching 0")
21+
void emptyGraph() {
22+
List<List<Integer>> g = adj(3);
23+
HopcroftKarp hk = new HopcroftKarp(3, 4, g);
24+
assertEquals(0, hk.maxMatching());
25+
}
26+
27+
@Test
28+
@DisplayName("Single edge gives matching 1")
29+
void singleEdge() {
30+
List<List<Integer>> g = adj(1);
31+
g.get(0).add(0);
32+
HopcroftKarp hk = new HopcroftKarp(1, 1, g);
33+
assertEquals(1, hk.maxMatching());
34+
35+
int[] L = hk.getLeftMatches();
36+
int[] R = hk.getRightMatches();
37+
assertEquals(0, L[0]);
38+
assertEquals(0, R[0]);
39+
}
40+
41+
@Test
42+
@DisplayName("Disjoint edges match perfectly")
43+
void disjointEdges() {
44+
// L0-R0, L1-R1, L2-R2
45+
List<List<Integer>> g = adj(3);
46+
g.get(0).add(0);
47+
g.get(1).add(1);
48+
g.get(2).add(2);
49+
50+
HopcroftKarp hk = new HopcroftKarp(3, 3, g);
51+
assertEquals(3, hk.maxMatching());
52+
53+
int[] L = hk.getLeftMatches();
54+
int[] R = hk.getRightMatches();
55+
for (int i = 0; i < 3; i++) {
56+
assertEquals(i, L[i]);
57+
assertEquals(i, R[i]);
58+
}
59+
}
60+
61+
@Test
62+
@DisplayName("Complete bipartite K(3,4) matches min(3,4)=3")
63+
void completeK34() {
64+
int nLeft = 3, nRight = 4;
65+
List<List<Integer>> g = adj(nLeft);
66+
for (int u = 0; u < nLeft; u++) {
67+
g.get(u).addAll(Arrays.asList(0, 1, 2, 3));
68+
}
69+
HopcroftKarp hk = new HopcroftKarp(nLeft, nRight, g);
70+
assertEquals(3, hk.maxMatching());
71+
72+
// sanity: no two left vertices share the same matched right vertex
73+
int[] L = hk.getLeftMatches();
74+
boolean[] used = new boolean[nRight];
75+
for (int u = 0; u < nLeft; u++) {
76+
int v = L[u];
77+
if (v != -1) {
78+
assertFalse(used[v]);
79+
used[v] = true;
80+
}
81+
}
82+
}
83+
84+
@Test
85+
@DisplayName("Non-square, sparse graph")
86+
void rectangularSparse() {
87+
// Left: 5, Right: 2
88+
// edges: L0-R0, L1-R1, L2-R0, L3-R1 (max matching = 2)
89+
List<List<Integer>> g = adj(5);
90+
g.get(0).add(0);
91+
g.get(1).add(1);
92+
g.get(2).add(0);
93+
g.get(3).add(1);
94+
// L4 isolated
95+
96+
HopcroftKarp hk = new HopcroftKarp(5, 2, g);
97+
assertEquals(2, hk.maxMatching());
98+
99+
int[] L = hk.getLeftMatches();
100+
int[] R = hk.getRightMatches();
101+
102+
// Check consistency: if L[u]=v then R[v]=u
103+
for (int u = 0; u < 5; u++) {
104+
int v = L[u];
105+
if (v != -1) {
106+
assertEquals(u, R[v]);
107+
}
108+
}
109+
}
110+
111+
@Test
112+
@DisplayName("Layering advantage case (chains of short augmenting paths)")
113+
void layeringAdvantage() {
114+
// Left 4, Right 4
115+
// Build a structure that benefits from BFS layering
116+
// L0: R0, R1
117+
// L1: R1, R2
118+
// L2: R2, R3
119+
// L3: R0, R3
120+
List<List<Integer>> g = adj(4);
121+
g.get(0).addAll(Arrays.asList(0, 1));
122+
g.get(1).addAll(Arrays.asList(1, 2));
123+
g.get(2).addAll(Arrays.asList(2, 3));
124+
g.get(3).addAll(Arrays.asList(0, 3));
125+
126+
HopcroftKarp hk = new HopcroftKarp(4, 4, g);
127+
assertEquals(4, hk.maxMatching()); // perfect matching exists
128+
}
129+
}

0 commit comments

Comments
 (0)