|
| 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