Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 657f433

Browse files
Hopcroft karp Algorithm implementation and tests (#6465)
* Add Hopcroft-Karp algorithm and tests * Adding wikipedia url for Algorithm * fixing test issues * remove unused field flagged by PMD
1 parent 57c6b03 commit 657f433

File tree

2 files changed

+236
-0
lines changed

2 files changed

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

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /