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