Hopcroft-Karp Algorithm

The Hopcroft-Karp Algorithm addresses the problem of finding a maximum cardinality matching in a bipartite graph. A bipartite graph is a graph whose vertices can be divided into two disjoint sets, say and , such that every edge connects a vertex in to a vertex in . A matching in this context is a set of edges where no two edges share a common vertex. The goal is to maximize the number of edges in the matching, ensuring the largest possible number of vertices in and are paired.

Task
Hopcroft-Karp Algorithm
You are encouraged to solve this task according to the task description, using any language you may know.

The problem can be formally stated as follows: Given a bipartite graph , where and are the two sets of vertices and is the set of edges, find a matching such that the size of (denoted ), the number of edges in the matching, is maximized. This is known as the maximum bipartite matching problem.

Mathematically, the objective is to maximize: subject to the constraint that for any two edges and in , and , ensuring no vertex is incident to more than one edge in the matching.


The pseudocode for the Hopcroft-Karp Algorithm looks like:

ALGORITHM Hopcroft-Karp(G, U, V)
    // G is the bipartite graph with partitions U and V
    // U is the left partition, V is the right partition
    // NIL represents an unmatched vertex (0)
    // INF represents infinity

    // Initialize matching arrays
    pair_u ← array of size |U| + 1 initialized with NIL
    pair_v ← array of size |V| + 1 initialized with NIL
    dist ← array of size |U| + 1 for distances
    matching_size ← 0

    WHILE BFS(G, pair_u, pair_v, dist) DO
        FOR each u ∈ U DO
            IF pair_u[u] = NIL THEN
                IF DFS(G, u, pair_u, pair_v, dist) THEN
                    matching_size ← matching_size + 1
    RETURN matching_size

FUNCTION BFS(G, pair_u, pair_v, dist)
    queue ← empty queue
    
    // Initialize distances
    FOR u ← 1 to |U| DO
        IF pair_u[u] = NIL THEN
            dist[u] ← 0
            enqueue(queue, u)
        ELSE
            dist[u] ← INF
    dist[NIL] ← INF
    
    WHILE queue not empty DO
        u ← dequeue(queue)
        IF dist[u] < dist[NIL] THEN
            FOR each v in G.adj[u] DO
                matched_u ← pair_v[v]
                IF dist[matched_u] = INF THEN
                    dist[matched_u] ← dist[u] + 1
                    enqueue(queue, matched_u)
    
    RETURN dist[NIL] ≠ INF

FUNCTION DFS(G, u, pair_u, pair_v, dist)
    IF u ≠ NIL THEN
        FOR each v in G.adj[u] DO
            matched_u ← pair_v[v]
            IF dist[matched_u] = dist[u] + 1 THEN
                IF DFS(G, matched_u, pair_u, pair_v, dist) THEN
                    pair_v[v] ← u
                    pair_u[u] ← v
                    RETURN TRUE
        dist[u] ← INF
        RETURN FALSE
    RETURN TRUE

// Graph Representation
// G.adj[u] contains the list of neighbors of vertex u in V
// pair_u[u] stores the vertex v matched with u (or NIL)
// pair_v[v] stores the vertex u matched with v (or NIL)
// dist[u] stores the distance of vertex u during BFS


Works with: C# version 8.0
Translation of: Java
using System;
using System.Collections.Generic;
using System.Linq;

public sealed class HopcroftKarpAlgorithm
{
    // Define Edge class instead of record
    private sealed class Edge
    {
        public int From { get; }
        public int To { get; }

        public Edge(int from, int to)
        {
            From = from;
            To = to;
        }
    }

    public static void Main(string[] args)
    {
        Console.WriteLine("Running tests:");
        int successCount = 0;

        // Test Case 1
        successCount = TestValue(1, 3, 5, new List<Edge> { new Edge(1, 4) }, 1);

        // Test Case 2
        successCount += TestValue(2, 6, 6, new List<Edge> { new Edge(1, 4), new Edge(1, 5), new Edge(5, 1) }, 2);

        // Test Case 3: Complete Bipartite Graph K(3, 3)
        var edges = new List<Edge>();
        for (int i = 1; i <= 3; i++)
        {
            for (int j = 1; j <= 3; j++)
            {
                edges.Add(new Edge(i, j));
            }
        }
        successCount += TestValue(3, 3, 3, edges, 3);

        // Test Case 4: No edges
        successCount += TestValue(4, 2, 2, new List<Edge>(), 0);

        // Test Case 5
        edges = new List<Edge>
        {
            new Edge(1, 1), new Edge(1, 3), new Edge(2, 3), new Edge(3, 4), new Edge(4, 3), new Edge(4, 2)
        };
        successCount += TestValue(5, 4, 4, edges, 4);

        if (successCount == 5)
        {
            Console.WriteLine("All tests passed.");
        }
    }

    private static int TestValue(int testNumber, int m, int n, List<Edge> edges, int expectedResult)
    {
        BipartiteGraph graph = new BipartiteGraph(m, n);
        foreach (var edge in edges)
        {
            graph.AddEdge(edge.From, edge.To);
        }
        int result = graph.HopcroftKarpAlgorithm();
        Console.WriteLine($"Test {testNumber}: Result = {result}, Expected = {expectedResult}");
        if (result == expectedResult)
        {
            return 1;
        }

        Console.WriteLine($"Test {testNumber} failed.");
        return 0;
    }
}

/// <summary>
/// Representation of a bipartite graph.
/// Vertices in the left partition, U, are numbered from 1 to m,
/// and vertices in the right partition, V, are numbered 1 to n.
/// </summary>
internal sealed class BipartiteGraph
{
    public BipartiteGraph(int aM, int aN)
    {
        m = aM;
        n = aN;

        adjacencyLists = Enumerable.Range(0, m + 1)
                                  .Select(i => new List<int>()).ToList();
        pairU = Enumerable.Repeat(NIL, m + 1).ToList();
        pairV = Enumerable.Repeat(NIL, n + 1).ToList();
        levels = Enumerable.Repeat(INFINITY, m + 1).ToList();
    }

    public void AddEdge(int u, int v)
    {
        if (1 <= u && u <= m && 1 <= v && v <= n)
        {
            adjacencyLists[u].Add(v);
        }
        else
        {
            throw new InvalidOperationException($"Attempt to add an edge ({u}, {v}) which is out of bounds");
        }
    }

    /// <summary>
    /// Return the matching size of the bipartite graph.
    /// </summary>
    public int HopcroftKarpAlgorithm()
    {
        pairU = Enumerable.Repeat(NIL, m + 1).ToList();
        pairV = Enumerable.Repeat(NIL, n + 1).ToList();
        int matchingSize = 0;

        while (BreadthFirstSearch())
        {
            for (int u = 1; u <= m; u++)
            {
                if (pairU[u] == NIL && DepthFirstSearch(u))
                {   // vertex u is free and an augmenting path starting                                         
                    matchingSize += 1;  // from u has been found by the depth first search
                }
            }
        }
        return matchingSize;
    }

    /// <summary>
    /// Determines whether there exists an augmenting path starting from a free vertex in U.
    /// 
    /// Return true if an augmenting path could exist, otherwise false.
    /// </summary>
    private bool BreadthFirstSearch()
    {
        Queue<int> queue = new Queue<int>();
        for (int u = 1; u <= m; u++)
        { // Initialise 'levels' for the vertices in U
            if (pairU[u] == NIL)
            { // If u is a free vertex, its level is 0 add it is added to the queue               
                levels[u] = 0;
                queue.Enqueue(u);
            }
            else
            { // Otherwise, set 'levels' to infinity                
                levels[u] = INFINITY;
            }
        }

        // The 'level' to the NIL node represents the length of the shortest augmenting path
        levels[NIL] = INFINITY;

        while (queue.Count > 0)
        {
            int u = queue.Dequeue();
            if (levels[u] < levels[NIL])
            { // The path through u could lead to a shorter augmenting path               
                foreach (int v in adjacencyLists[u])
                { // Explore the neighbours v of u in V
                    int matchedU = pairV[v];
                    if (levels[matchedU] == INFINITY)
                    { // The matched vertex has not been visited yet
                        levels[matchedU] = levels[u] + 1;
                        queue.Enqueue(matchedU); // Enqueue the matched vertex to explore it further
                    }
                }
            }
        }

        // An augmenting path from the initial free vertices was found if levels[NIL] is not INFINITY
        return levels[NIL] != INFINITY;
    }

    /// <summary>
    /// Determine whether the shortest path from vertex u in U found by BreadthFirstSearch() can be augmented.
    ///
    /// Return true if an augmenting path was found starting from u, otherwise false.
    /// </summary>
    private bool DepthFirstSearch(int u)
    {
        if (u != NIL)
        {
            foreach (int v in adjacencyLists[u])
            { // Explore neighbours v of u in V
                int matchedU = pairV[v];
                // Check whether the edge (u, v) leads to a vertex matchedU
                // such that the path u -> v -> matchedU is part of a shortest augmenting path
                if (levels[matchedU] == levels[u] + 1)
                {
                    if (DepthFirstSearch(matchedU))
                    { // An augmenting path is found starting from 'matchedU'
                        pairV[v] = u; // Match v with u,
                        pairU[u] = v; // and u with v
                        return true;
                    }
                }
            }

            // No augmenting path was found starting from vertex u through any of its neighbours v,
            // so remove u from the depth first search phase of the algorithm
            levels[u] = INFINITY;
            return false;
        }

        return true;
    }

    private List<List<int>> adjacencyLists; // adjacencyLists(u) stores a list of neighbours of u in V
    private List<int> pairU; // pairU(u) stores the vertex v in V matched with u in U, or NIL if unmatched
    private List<int> pairV; // pairV(v) stores the vertex u in U matched with v in V, or NIL if unmatched
    private List<int> levels; // levels(u) stores the level of vertex u in U during a breadth first search

    private readonly int m; // Index of the vertices in the left partition
    private readonly int n; // Index of the vertices in the right partition

    private const int NIL = 0;
    private const int INFINITY = int.MaxValue;
}
Output:
Running tests:
Test 1: Result = 1, Expected = 1
Test 2: Result = 2, Expected = 2
Test 3: Result = 3, Expected = 3
Test 4: Result = 0, Expected = 0
Test 5: Result = 4, Expected = 4
All tests passed.


#include <cstdint>
#include <limits>
#include <iostream>
#include <queue>
#include <stdexcept>
#include <string>
#include <vector>

/**
 * Representation of a bipartite graph.
 * Vertices in the left partition, U, are numbered from 1 to m,
 * and vertices in the right partition, V, are numbered 1 to n.
 */
class BipartiteGraph {
public:
	BipartiteGraph(const uint32_t& aM, const uint32_t& aN) {
		m = aM;
		n = aN;

		adjacency_lists = { m + 1, std::vector<uint32_t>() };
		pair_u.assign(m + 1, NIL);
		pair_v.assign(n + 1, NIL);
		levels.assign(m + 1, INFINITY);
	}

	void add_edge(const uint32_t& u, const uint32_t& v) {
		if ( 1 <= u && u <= m && 1 <= v && v <= n ) {
			adjacency_lists[u].emplace_back(v);
		} else {
			throw std::invalid_argument("Attempt to add an edge (" +
				std::to_string(u) + ", " + std::to_string(v) + ") which is out of bounds");
		}
	}

	/**
	 * Return the matching size of the bipartite graph.
	 */
	uint32_t hopcroftKarp_algorithm() {
		pair_u.assign(m + 1, NIL);
		pair_v.assign(n + 1, NIL);
		uint32_t matching_size = 0;

		while ( breadth_first_search() ) {
			for ( uint32_t u = 1; u <= m; ++u ) {
				if ( pair_u[u] == NIL && depth_first_search(u) ) { // vertex u is free and an augmenting path starting
					matching_size++;                               // from u has been found by the depth first search
				}
			}
		}
		return matching_size;
	}

private:
	/**
	 * Determines whether there exists an augmenting path starting from a free vertex in U.
	 *
	 * Return true if an augmenting path could exist, otherwise false.
	 */
	bool breadth_first_search() {
		std::queue<uint32_t> queue;
		for ( uint32_t u = 1; u <= m; ++u ) { // Initialise 'levels' for the vertices in U
			if ( pair_u[u] == NIL ) { // If u is a free vertex, its level is 0 add it is added to the queue
				levels[u] = 0;
				queue.push(u);
			} else { // Otherwise, set 'levels' to infinity
				levels[u] = INFINITY;
			}
		}

		// The 'level' to the NIL node represents the length of the shortest augmenting path
		levels[NIL] = INFINITY;

		while ( ! queue.empty() ) {
			const uint32_t u = queue.front();
			queue.pop();
			if ( levels[u] < levels[NIL] ) { // The path through u could lead to a shorter augmenting path
				for ( const uint32_t& v : adjacency_lists[u] ) { // Explore the neighbours v of u in V
					const uint32_t matched_u = pair_v[v];
					if ( levels[matched_u] == INFINITY ) { // The matched vertex has not been visited yet
					   levels[matched_u] = levels[u] + 1;
						queue.push(matched_u); // Enqueue the matched vertex to explore it further
					}
				}
			}
		}

		// An augmenting path from the initial free vertices was found if levels[NIL] is not INFINITY
		return levels[NIL] != INFINITY;
	}

	/**
	 * Determine whether the shortest path from vertex u in U found by breadth_first_search() can be augmented.
	 *
	 * Return true if an augmenting path was found starting from u, otherwise false.
	 */
	bool depth_first_search(const uint32_t& u) {
		if ( u != NIL ) {
			for ( const uint32_t& v : adjacency_lists[u] ) { // Explore neighbours v of u in V
				const uint32_t matched_u = pair_v[v];
				// Check whether the edge (u, v) leads to a vertex matched_u
				// such that the path u -> v -> matched_u is part of a shortest augmenting path
				if ( levels[matched_u] == levels[u] + 1 ) {
					if ( depth_first_search(matched_u) ) { // An augmenting path is found starting from 'matched_u'
						pair_v[v] = u; // Match v with u,
						pair_u[u] = v; // and u with v
						return true;
					}
				}
			}

			// No augmenting path was found starting from vertex u through any of its neighbours v,
			// so remove u from the depth first search phase of the algorithm
			levels[u] = INFINITY;
			return false;
		}

		return true;
	}

	std::vector<std::vector<uint32_t>> adjacency_lists; // adjacency_lists(u) stores a list of neighbours of u in V
	std::vector<uint32_t> pair_u; // pair_u(u) stores the vertex v in V matched with u in U, or NIL if unmatched
	std::vector<uint32_t> pair_v; // pair_v(v) stores the vertex u in U matched with v in V, or NIL if unmatched
	std::vector<uint32_t> levels; // levels(u) stores the level of vertex u in U during a breadth first search

	uint32_t m; // Index of the vertices in the left partition
	uint32_t n; // Index of the vertices in the right partition

	const uint32_t NIL = 0;
	const uint32_t INFINITY = INT_MAX;
};

struct Edge {
	uint32_t from;
	uint32_t to;
};

uint32_t test_value(const uint32_t& testNumber, const uint32_t& m, const uint32_t& n,
		            const std::vector<Edge>& edges, const uint32_t& expected_result) {
	BipartiteGraph graph(m, n);
	for ( const Edge& edge : edges ) {
		graph.add_edge(edge.from, edge.to);
	}
	const uint32_t result = graph.hopcroftKarp_algorithm();
	std::cout << "Test " << testNumber << ": Result = " << result << ", Expected = " << expected_result << std::endl;
	if ( result == expected_result ) {
		return 1;
	}

	std::cout << "Test " << testNumber << " failed." << std::endl;
	return 0;
}

int main() {
	std::cout << "Running tests:" << std::endl;
	uint32_t success_count = 0;

	// Test Case 1
	success_count = test_value(1, 3, 5, { Edge(1, 4) }, 1);

	// Test Case 2
	success_count += test_value(2, 6, 6, { Edge(1, 4), Edge(1, 5), Edge(5, 1) }, 2);

	// Test Case 3: Complete Bipartite Graph K(3, 3)
	std::vector<Edge> edges;
	for ( uint32_t i = 1; i <= 3; ++i ) {
		for ( uint32_t j = 1; j <= 3; ++j ) {
			edges.emplace_back( Edge(i, j) );
		}
	}
	success_count += test_value(3, 3, 3, edges, 3);

	// Test Case 4: No edges
	success_count += test_value(4, 2, 2, { }, 0);

	// Test Case 5
	edges = { Edge(1, 1), Edge(1, 3), Edge(2, 3), Edge(3, 4), Edge(4, 3), Edge(4, 2) };
	success_count += test_value(5, 4, 4, edges, 4);

	if ( success_count == 5 ) {
		std::cout << "All tests passed." << std::endl;
	}
}
Output:
Running tests:
Test 1: Result = 1, Expected = 1
Test 2: Result = 2, Expected = 2
Test 3: Result = 3, Expected = 3
Test 4: Result = 0, Expected = 0
Test 5: Result = 4, Expected = 4
All tests passed.


Translation of: Java
Works with: COBOL
IDENTIFICATION DIVISION.
       PROGRAM-ID. HOPCROFT-KARP.
       AUTHOR. TRANSLATED-FROM-JAVA.

      *>================================================================
      *> Hopcroft-Karp Maximum Bipartite Matching Algorithm
      *>
      *> Left partition U: vertices 1..M
      *> Right partition V: vertices 1..N
      *>
      *> Limitations of this COBOL implementation:
      *>   MAX-M   = max vertices in U (left)
      *>   MAX-N   = max vertices in V (right)
      *>   MAX-ADJ = max total directed edges
      *>   QUEUE   = BFS queue size
      *>================================================================

       ENVIRONMENT DIVISION.
       CONFIGURATION SECTION.
       SOURCE-COMPUTER. ANY-COMPUTER.
       OBJECT-COMPUTER. ANY-COMPUTER.

       DATA DIVISION.
       WORKING-STORAGE SECTION.

      *>--- Graph dimensions ---
       01  WS-M            PIC 9(4) VALUE 0.
       01  WS-N            PIC 9(4) VALUE 0.
       01  WS-MATCH-SIZE   PIC 9(4) VALUE 0.

      *>--- Constants ---
       01  WS-NIL          PIC 9(4) VALUE 0.
       01  WS-INFINITY     PIC 9(9) VALUE 999999999.
       01  WS-MAX-M        PIC 9(4) VALUE 50.
       01  WS-MAX-N        PIC 9(4) VALUE 50.
       01  WS-MAX-ADJ      PIC 9(6) VALUE 2500.

      *>--- Adjacency list (CSR-style): adj-dest(i) = destination vertex
      *>    adj-start(u) = first index in adj-dest for vertex u
      *>    adj-end(u)   = one past last index                     ---
       01  WS-ADJ-DEST     OCCURS 2500 TIMES PIC 9(4) VALUE 0.
       01  WS-ADJ-COUNT    PIC 9(6) VALUE 0.

      *>--- Per-vertex adjacency bounds (indexed 0..MAX-M) ---
       01  WS-ADJ-START    OCCURS 51 TIMES PIC 9(6) VALUE 0.
       01  WS-ADJ-LEN      OCCURS 51 TIMES PIC 9(4) VALUE 0.

      *>--- Temporary edge list before building CSR ---
       01  WS-EDGE-TABLE.
           05  WS-EDGE     OCCURS 2500 TIMES.
               10  WS-EDGE-FROM  PIC 9(4) VALUE 0.
               10  WS-EDGE-TO    PIC 9(4) VALUE 0.
       01  WS-EDGE-COUNT   PIC 9(6) VALUE 0.

      *>--- Matching arrays ---
       01  WS-PAIR-U       OCCURS 51 TIMES PIC 9(4) VALUE 0.
       01  WS-PAIR-V       OCCURS 51 TIMES PIC 9(4) VALUE 0.

      *>--- Level array (indexed 0..MAX-M, index 0 = NIL node) ---
       01  WS-LEVEL        OCCURS 51 TIMES PIC 9(9) VALUE 0.

      *>--- BFS Queue ---
       01  WS-QUEUE        OCCURS 5100 TIMES PIC 9(4) VALUE 0.
       01  WS-Q-HEAD       PIC 9(6) VALUE 1.
       01  WS-Q-TAIL       PIC 9(6) VALUE 0.

      *>--- Loop / work variables ---
       01  WS-U            PIC 9(4) VALUE 0.
       01  WS-V            PIC 9(4) VALUE 0.
       01  WS-MATCHED-U    PIC 9(4) VALUE 0.
       01  WS-BFS-RESULT   PIC 9(1) VALUE 0.
       01  WS-DFS-RESULT   PIC 9(1) VALUE 0.
       01  WS-I            PIC 9(6) VALUE 0.
       01  WS-J            PIC 9(4) VALUE 0.
       01  WS-IDX          PIC 9(6) VALUE 0.

      *>--- DFS recursion stack (simulate recursion) ---
       01  WS-STACK        OCCURS 51 TIMES PIC 9(4) VALUE 0.
       01  WS-STACK-POS    OCCURS 51 TIMES PIC 9(6) VALUE 0.
       01  WS-STACK-TOP    PIC 9(4) VALUE 0.
       01  WS-STACK-V      PIC 9(4) VALUE 0.
       01  WS-STACK-U2     PIC 9(4) VALUE 0.

      *>--- Test harness ---
       01  WS-TEST-NUM     PIC 9(2) VALUE 0.
       01  WS-EXPECTED     PIC 9(4) VALUE 0.
       01  WS-SUCCESS-CNT  PIC 9(2) VALUE 0.
       01  WS-PASS-FAIL    PIC X(6) VALUE SPACES.
       01  WS-ALL-PASS     PIC 9(2) VALUE 5.

      *>--- Display work ---
       01  WS-DISP-NUM     PIC Z(4).

       PROCEDURE DIVISION.

       MAIN-PARA.
           DISPLAY "Running tests:"
           MOVE 0 TO WS-SUCCESS-CNT

      *>--- Test 1: M=3 N=5, edge (1,4), expected=1 ---
           MOVE 1 TO WS-TEST-NUM
           MOVE 3 TO WS-M
           MOVE 5 TO WS-N
           MOVE 0 TO WS-EDGE-COUNT
           PERFORM INIT-EDGE-TABLE
           MOVE 1 TO WS-EDGE-COUNT
           MOVE 1 TO WS-EDGE-FROM(1)
           MOVE 4 TO WS-EDGE-TO(1)
           MOVE 1 TO WS-EXPECTED
           PERFORM RUN-TEST
           ADD WS-DFS-RESULT TO WS-SUCCESS-CNT

      *>--- Test 2: M=6 N=6, edges (1,4)(1,5)(5,1), expected=2 ---
           MOVE 2 TO WS-TEST-NUM
           MOVE 6 TO WS-M
           MOVE 6 TO WS-N
           PERFORM INIT-EDGE-TABLE
           MOVE 3 TO WS-EDGE-COUNT
           MOVE 1 TO WS-EDGE-FROM(1)
           MOVE 4 TO WS-EDGE-TO(1)
           MOVE 1 TO WS-EDGE-FROM(2)
           MOVE 5 TO WS-EDGE-TO(2)
           MOVE 5 TO WS-EDGE-FROM(3)
           MOVE 1 TO WS-EDGE-TO(3)
           MOVE 2 TO WS-EXPECTED
           PERFORM RUN-TEST
           ADD WS-DFS-RESULT TO WS-SUCCESS-CNT

      *>--- Test 3: K(3,3) complete bipartite, expected=3 ---
           MOVE 3 TO WS-TEST-NUM
           MOVE 3 TO WS-M
           MOVE 3 TO WS-N
           PERFORM INIT-EDGE-TABLE
           MOVE 0 TO WS-EDGE-COUNT
           PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 3
               PERFORM VARYING WS-J FROM 1 BY 1 UNTIL WS-J > 3
                   ADD 1 TO WS-EDGE-COUNT
                   MOVE WS-I TO WS-EDGE-FROM(WS-EDGE-COUNT)
                   MOVE WS-J TO WS-EDGE-TO(WS-EDGE-COUNT)
               END-PERFORM
           END-PERFORM
           MOVE 3 TO WS-EXPECTED
           PERFORM RUN-TEST
           ADD WS-DFS-RESULT TO WS-SUCCESS-CNT

      *>--- Test 4: No edges, expected=0 ---
           MOVE 4 TO WS-TEST-NUM
           MOVE 2 TO WS-M
           MOVE 2 TO WS-N
           PERFORM INIT-EDGE-TABLE
           MOVE 0 TO WS-EDGE-COUNT
           MOVE 0 TO WS-EXPECTED
           PERFORM RUN-TEST
           ADD WS-DFS-RESULT TO WS-SUCCESS-CNT

      *>--- Test 5: M=4 N=4, 6 edges, expected=4 ---
           MOVE 5 TO WS-TEST-NUM
           MOVE 4 TO WS-M
           MOVE 4 TO WS-N
           PERFORM INIT-EDGE-TABLE
           MOVE 6 TO WS-EDGE-COUNT
           MOVE 1 TO WS-EDGE-FROM(1)  MOVE 1 TO WS-EDGE-TO(1)
           MOVE 1 TO WS-EDGE-FROM(2)  MOVE 3 TO WS-EDGE-TO(2)
           MOVE 2 TO WS-EDGE-FROM(3)  MOVE 3 TO WS-EDGE-TO(3)
           MOVE 3 TO WS-EDGE-FROM(4)  MOVE 4 TO WS-EDGE-TO(4)
           MOVE 4 TO WS-EDGE-FROM(5)  MOVE 3 TO WS-EDGE-TO(5)
           MOVE 4 TO WS-EDGE-FROM(6)  MOVE 2 TO WS-EDGE-TO(6)
           MOVE 4 TO WS-EXPECTED
           PERFORM RUN-TEST
           ADD WS-DFS-RESULT TO WS-SUCCESS-CNT

           IF WS-SUCCESS-CNT = WS-ALL-PASS
               DISPLAY "All tests passed."
           END-IF

           STOP RUN.

      *>================================================================
      *> INIT-EDGE-TABLE: clear edge staging area
      *>================================================================
       INIT-EDGE-TABLE.
           MOVE 0 TO WS-EDGE-COUNT
           PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 2500
               MOVE 0 TO WS-EDGE-FROM(WS-EDGE-COUNT + 1)
               MOVE 0 TO WS-EDGE-TO(WS-EDGE-COUNT + 1)
               ADD 1 TO WS-EDGE-COUNT
           END-PERFORM
           MOVE 0 TO WS-EDGE-COUNT.

      *>================================================================
      *> RUN-TEST: build graph from edge table, run algorithm, report
      *>   Input:  WS-M, WS-N, WS-EDGE-COUNT/FROM/TO, WS-EXPECTED
      *>   Output: WS-DFS-RESULT = 1 if passed, 0 if failed
      *>================================================================
       RUN-TEST.
           PERFORM BUILD-GRAPH
           PERFORM HOPCROFT-KARP
           MOVE WS-DISP-NUM TO WS-DISP-NUM
           MOVE WS-MATCH-SIZE TO WS-DISP-NUM
           IF WS-MATCH-SIZE = WS-EXPECTED
               MOVE "PASSED" TO WS-PASS-FAIL
               MOVE 1 TO WS-DFS-RESULT
           ELSE
               MOVE "FAILED" TO WS-PASS-FAIL
               MOVE 0 TO WS-DFS-RESULT
           END-IF
           DISPLAY "Test " WS-TEST-NUM ": Result = " WS-MATCH-SIZE
               " Expected = " WS-EXPECTED " " WS-PASS-FAIL.

      *>================================================================
      *> BUILD-GRAPH: convert edge list to CSR adjacency structure
      *>================================================================
       BUILD-GRAPH.
      *> Clear adjacency lengths
           PERFORM VARYING WS-I FROM 0 BY 1 UNTIL WS-I > 50
               MOVE 0 TO WS-ADJ-LEN(WS-I + 1)
           END-PERFORM

      *> Count edges per source vertex
           PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > WS-EDGE-COUNT
               MOVE WS-EDGE-FROM(WS-I) TO WS-U
               ADD 1 TO WS-ADJ-LEN(WS-U + 1)
           END-PERFORM

      *> Compute start offsets (1-based in adj-dest)
           MOVE 1 TO WS-ADJ-START(1)       *> index 0 = NIL, unused
           MOVE 1 TO WS-ADJ-START(2)       *> vertex 1 starts at 1
           PERFORM VARYING WS-U FROM 1 BY 1 UNTIL WS-U > 49
               COMPUTE WS-ADJ-START(WS-U + 2) =
                   WS-ADJ-START(WS-U + 1) + WS-ADJ-LEN(WS-U + 1)
           END-PERFORM

      *> Place edges into adj-dest; use a temp cursor array
           PERFORM VARYING WS-U FROM 0 BY 1 UNTIL WS-U > 50
               MOVE WS-ADJ-START(WS-U + 1) TO WS-ADJ-LEN(WS-U + 1)
           END-PERFORM
           PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > WS-EDGE-COUNT
               MOVE WS-EDGE-FROM(WS-I) TO WS-U
               MOVE WS-EDGE-TO(WS-I)   TO WS-V
               MOVE WS-ADJ-LEN(WS-U + 1) TO WS-IDX
               MOVE WS-V TO WS-ADJ-DEST(WS-IDX)
               ADD 1 TO WS-ADJ-LEN(WS-U + 1)
           END-PERFORM.

      *>================================================================
      *> HOPCROFT-KARP: main algorithm
      *>================================================================
       HOPCROFT-KARP.
      *> Initialise pairU and pairV to NIL
           PERFORM VARYING WS-U FROM 0 BY 1 UNTIL WS-U > 50
               MOVE 0 TO WS-PAIR-U(WS-U + 1)
               MOVE 0 TO WS-PAIR-V(WS-U + 1)
           END-PERFORM
           MOVE 0 TO WS-MATCH-SIZE

           PERFORM BFS-PHASE
           PERFORM UNTIL WS-BFS-RESULT = 0
               PERFORM VARYING WS-U FROM 1 BY 1 UNTIL WS-U > WS-M
                   IF WS-PAIR-U(WS-U + 1) = WS-NIL
                       PERFORM DFS-PHASE
                       IF WS-DFS-RESULT = 1
                           ADD 1 TO WS-MATCH-SIZE
                       END-IF
                   END-IF
               END-PERFORM
               PERFORM BFS-PHASE
           END-PERFORM.

      *>================================================================
      *> BFS-PHASE: layered BFS over free vertices in U
      *>   Sets WS-BFS-RESULT = 1 if augmenting path found, else 0
      *>   Uses WS-U as loop variable (saved/restored via WS-STACK)
      *>================================================================
       BFS-PHASE.
           MOVE 1 TO WS-Q-HEAD
           MOVE 0 TO WS-Q-TAIL

      *> Initialise levels
           PERFORM VARYING WS-U FROM 0 BY 1 UNTIL WS-U > 50
               MOVE WS-INFINITY TO WS-LEVEL(WS-U + 1)
           END-PERFORM

      *> Enqueue free vertices of U
           PERFORM VARYING WS-U FROM 1 BY 1 UNTIL WS-U > WS-M
               IF WS-PAIR-U(WS-U + 1) = WS-NIL
                   MOVE 0 TO WS-LEVEL(WS-U + 1)
                   ADD 1 TO WS-Q-TAIL
                   MOVE WS-U TO WS-QUEUE(WS-Q-TAIL)
               END-IF
           END-PERFORM

      *> BFS loop
           PERFORM UNTIL WS-Q-HEAD > WS-Q-TAIL
               MOVE WS-QUEUE(WS-Q-HEAD) TO WS-U
               ADD 1 TO WS-Q-HEAD
               IF WS-LEVEL(WS-U + 1) < WS-LEVEL(1)
      *>            1 = index for NIL (index 0+1)
                   PERFORM VARYING WS-IDX FROM WS-ADJ-START(WS-U + 1)
                                   BY 1
                                   UNTIL WS-IDX >=
                                         WS-ADJ-START(WS-U + 2)
                       MOVE WS-ADJ-DEST(WS-IDX) TO WS-V
                       MOVE WS-PAIR-V(WS-V + 1) TO WS-MATCHED-U
                       IF WS-LEVEL(WS-MATCHED-U + 1) = WS-INFINITY
                           COMPUTE WS-LEVEL(WS-MATCHED-U + 1) =
                               WS-LEVEL(WS-U + 1) + 1
                           IF WS-MATCHED-U NOT = WS-NIL
                               ADD 1 TO WS-Q-TAIL
                               MOVE WS-MATCHED-U TO
                                   WS-QUEUE(WS-Q-TAIL)
                           END-IF
                       END-IF
                   END-PERFORM
               END-IF
           END-PERFORM

           IF WS-LEVEL(1) = WS-INFINITY
               MOVE 0 TO WS-BFS-RESULT
           ELSE
               MOVE 1 TO WS-BFS-RESULT
           END-IF.

      *>================================================================
      *> DFS-PHASE: augmenting path DFS from vertex WS-U
      *>   Input:  WS-U  (the starting free vertex)
      *>   Output: WS-DFS-RESULT = 1 if augmenting path found, 0 if not
      *>
      *>   We simulate the recursive DFS with an explicit stack.
      *>   WS-STACK(k)     = current U-node at depth k
      *>   WS-STACK-POS(k) = next adj-list index to try at depth k
      *>================================================================
       DFS-PHASE.
           MOVE 1 TO WS-STACK-TOP
           MOVE WS-U TO WS-STACK(1)
           MOVE WS-ADJ-START(WS-U + 1) TO WS-STACK-POS(1)
           MOVE 0 TO WS-DFS-RESULT

           PERFORM UNTIL WS-STACK-TOP = 0
               MOVE WS-STACK(WS-STACK-TOP)     TO WS-STACK-U2
               MOVE WS-STACK-POS(WS-STACK-TOP) TO WS-IDX

               IF WS-STACK-U2 = WS-NIL
      *>            Reached NIL: augmenting path found; unwind
                   MOVE 1 TO WS-DFS-RESULT
                   SUBTRACT 1 FROM WS-STACK-TOP
                   PERFORM UNTIL WS-STACK-TOP = 0
      *>                Pair the edge we followed to reach NIL parent
                       MOVE WS-STACK-TOP TO WS-J
                       SUBTRACT 1 FROM WS-STACK-POS(WS-J)
                       MOVE WS-STACK-POS(WS-J) TO WS-IDX
                       MOVE WS-ADJ-DEST(WS-IDX) TO WS-STACK-V
                       MOVE WS-STACK(WS-J) TO WS-U
                       MOVE WS-U TO WS-PAIR-V(WS-STACK-V + 1)
                       MOVE WS-STACK-V TO WS-PAIR-U(WS-U + 1)
                       SUBTRACT 1 FROM WS-STACK-TOP
                   END-PERFORM
               ELSE
      *>            Try next edge from WS-STACK-U2
                   IF WS-IDX < WS-ADJ-START(WS-STACK-U2 + 2)
                       MOVE WS-ADJ-DEST(WS-IDX) TO WS-V
                       ADD 1 TO WS-STACK-POS(WS-STACK-TOP)
                       MOVE WS-PAIR-V(WS-V + 1) TO WS-MATCHED-U
                       IF WS-LEVEL(WS-MATCHED-U + 1) =
                               WS-LEVEL(WS-STACK-U2 + 1) + 1
      *>                    Follow this edge: push matched-U onto stack
                           ADD 1 TO WS-STACK-TOP
                           MOVE WS-MATCHED-U TO WS-STACK(WS-STACK-TOP)
                           IF WS-MATCHED-U = WS-NIL
                               MOVE 1 TO
                                   WS-STACK-POS(WS-STACK-TOP)
                           ELSE
                               MOVE WS-ADJ-START(WS-MATCHED-U + 1) TO
                                   WS-STACK-POS(WS-STACK-TOP)
                           END-IF
                       END-IF
                   ELSE
      *>                No more edges: dead end, set level to infinity
                       MOVE WS-INFINITY TO
                           WS-LEVEL(WS-STACK-U2 + 1)
                       SUBTRACT 1 FROM WS-STACK-TOP
                   END-IF
               END-IF
           END-PERFORM.

       END PROGRAM HOPCROFT-KARP.
Output:
Running tests:
Test 01: Result = 0001 Expected = 0001 PASSED
Test 02: Result = 0002 Expected = 0002 PASSED
Test 03: Result = 0003 Expected = 0003 PASSED
Test 04: Result = 0000 Expected = 0000 PASSED
Test 05: Result = 0004 Expected = 0004 PASSED
All tests passed.
Works with: Dart version 3.6.1
Translation of: C++
import 'dart:collection';
import 'dart:math';

/// Representation of a bipartite graph.
/// Vertices in the left partition, U, are numbered from 1 to m,
/// and vertices in the right partition, V, are numbered 1 to n.
class BipartiteGraph {
  late int m; // Index of the vertices in the left partition
  late int n; // Index of the vertices in the right partition

  final int NIL = 0;
  final int INFINITY = 2147483647; // INT_MAX

  late List<List<int>> adjacencyLists; // adjacencyLists[u] stores a list of neighbours of u in V
  late List<int> pairU; // pairU[u] stores the vertex v in V matched with u in U, or NIL if unmatched
  late List<int> pairV; // pairV[v] stores the vertex u in U matched with v in V, or NIL if unmatched
  late List<int> levels; // levels[u] stores the level of vertex u in U during a breadth first search

  BipartiteGraph(int aM, int aN) {
    m = aM;
    n = aN;

    adjacencyLists = List.generate(m + 1, (index) => <int>[], growable: false);
    pairU = List.filled(m + 1, NIL, growable: false);
    pairV = List.filled(n + 1, NIL, growable: false);
    levels = List.filled(m + 1, INFINITY, growable: false);
  }

  void addEdge(int u, int v) {
    if (1 <= u && u <= m && 1 <= v && v <= n) {
      adjacencyLists[u].add(v);
    } else {
      throw ArgumentError('Attempt to add an edge ($u, $v) which is out of bounds');
    }
  }

  /// Return the matching size of the bipartite graph.
  int hopcroftKarpAlgorithm() {
    pairU = List.filled(m + 1, NIL, growable: false);
    pairV = List.filled(n + 1, NIL, growable: false);
    int matchingSize = 0;

    while (breadthFirstSearch()) {
      for (int u = 1; u <= m; u++) {
        if (pairU[u] == NIL && depthFirstSearch(u)) { // vertex u is free and an augmenting path starting
          matchingSize++;                             // from u has been found by the depth first search
        }
      }
    }
    return matchingSize;
  }

  /// Determines whether there exists an augmenting path starting from a free vertex in U.
  ///
  /// Return true if an augmenting path could exist, otherwise false.
  bool breadthFirstSearch() {
    Queue<int> queue = Queue();
    for (int u = 1; u <= m; u++) { // Initialise 'levels' for the vertices in U
      if (pairU[u] == NIL) { // If u is a free vertex, its level is 0 and it is added to the queue
        levels[u] = 0;
        queue.add(u);
      } else { // Otherwise, set 'levels' to infinity
        levels[u] = INFINITY;
      }
    }

    // The 'level' to the NIL node represents the length of the shortest augmenting path
    levels[NIL] = INFINITY;

    while (queue.isNotEmpty) {
      final int u = queue.removeFirst();
      if (levels[u] < levels[NIL]) { // The path through u could lead to a shorter augmenting path
        for (final int v in adjacencyLists[u]) { // Explore the neighbours v of u in V
          final int matchedU = pairV[v];
          if (levels[matchedU] == INFINITY) { // The matched vertex has not been visited yet
            levels[matchedU] = levels[u] + 1;
            queue.add(matchedU); // Enqueue the matched vertex to explore it further
          }
        }
      }
    }

    // An augmenting path from the initial free vertices was found if levels[NIL] is not INFINITY
    return levels[NIL] != INFINITY;
  }

  /// Determine whether the shortest path from vertex u in U found by breadthFirstSearch() can be augmented.
  ///
  /// Return true if an augmenting path was found starting from u, otherwise false.
  bool depthFirstSearch(int u) {
    if (u != NIL) {
      for (final int v in adjacencyLists[u]) { // Explore neighbours v of u in V
        final int matchedU = pairV[v];
        // Check whether the edge (u, v) leads to a vertex matchedU
        // such that the path u -> v -> matchedU is part of a shortest augmenting path
        if (levels[matchedU] == levels[u] + 1) {
          if (depthFirstSearch(matchedU)) { // An augmenting path is found starting from 'matchedU'
            pairV[v] = u; // Match v with u,
            pairU[u] = v; // and u with v
            return true;
          }
        }
      }

      // No augmenting path was found starting from vertex u through any of its neighbours v,
      // so remove u from the depth first search phase of the algorithm
      levels[u] = INFINITY;
      return false;
    }

    return true;
  }
}

class Edge {
  final int from;
  final int to;

  Edge(this.from, this.to);
}

int testValue(int testNumber, int m, int n, List<Edge> edges, int expectedResult) {
  BipartiteGraph graph = BipartiteGraph(m, n);
  for (final Edge edge in edges) {
    graph.addEdge(edge.from, edge.to);
  }
  final int result = graph.hopcroftKarpAlgorithm();
  print('Test $testNumber: Result = $result, Expected = $expectedResult');
  if (result == expectedResult) {
    return 1;
  }

  print('Test $testNumber failed.');
  return 0;
}

void main() {
  print('Running tests:');
  int successCount = 0;

  // Test Case 1
  successCount = testValue(1, 3, 5, [Edge(1, 4)], 1);

  // Test Case 2
  successCount += testValue(2, 6, 6, [Edge(1, 4), Edge(1, 5), Edge(5, 1)], 2);

  // Test Case 3: Complete Bipartite Graph K(3, 3)
  List<Edge> edges = [];
  for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 3; j++) {
      edges.add(Edge(i, j));
    }
  }
  successCount += testValue(3, 3, 3, edges, 3);

  // Test Case 4: No edges
  successCount += testValue(4, 2, 2, [], 0);

  // Test Case 5
  edges = [Edge(1, 1), Edge(1, 3), Edge(2, 3), Edge(3, 4), Edge(4, 3), Edge(4, 2)];
  successCount += testValue(5, 4, 4, edges, 4);

  if (successCount == 5) {
    print('All tests passed.');
  }
}
Output:
Running tests:
Test 1: Result = 1, Expected = 1
Test 2: Result = 2, Expected = 2
Test 3: Result = 3, Expected = 3
Test 4: Result = 0, Expected = 0
Test 5: Result = 4, Expected = 4
All tests passed.



Translation of: Python
' Constants
Const NULL As Any Ptr = 0
Const NIL As Integer = 0
Const INF As Double = 1e308 ' Equivalent to float('inf') in Python
Dim Shared success As Integer = 0

Type HKGraph
    m As Integer ' Number of vertices in left partition (U)
    n As Integer ' Number of vertices in right partition (V)
    
    ' Adjacency list: adj[u] contains list of neighbors of u in V
    ' We'll use dynamic arrays to simulate the Python lists
    adj As Integer Ptr Ptr
    adj_size As Integer Ptr ' Size of each adjacency list
    adj_capacity As Integer Ptr ' Capacity of each adjacency list
    
    ' Matching pairs
    pair_u As Integer Ptr ' pair_u[u] stores the vertex v in V matched with u in U
    pair_v As Integer Ptr ' pair_v[v] stores the vertex u in U matched with v in V
    
    ' Distance array for BFS
    dist As Double Ptr ' dist[u] stores the distance of vertex u in U during BFS
    
    Declare Constructor(m As Integer, n As Integer)
    Declare Destructor()
    Declare Sub add_edge(u As Integer, v As Integer)
    Declare Function bfs() As Boolean
    Declare Function dfs(u As Integer) As Boolean
    Declare Function hopcroft_karp_algorithm() As Integer
End Type

' Queue implementation for BFS
Type Queue
    dato As Integer Ptr
    front As Integer
    rear As Integer
    capacity As Integer
    size As Integer
    
    Declare Constructor()
    Declare Destructor()
    Declare Sub enqueue(item As Integer)
    Declare Function dequeue() As Integer
    Declare Function isEmpty() As Boolean
End Type

Constructor Queue()
    capacity = 1000 ' Initial capacity
    dato = Allocate(capacity * Sizeof(Integer))
    front = 0
    rear = -1
    size = 0
End Constructor

Destructor Queue()
    If dato <> NULL Then Deallocate(dato)
End Destructor

Sub Queue.enqueue(item As Integer)
    If size = capacity Then
        ' Resize the queue if needed
        capacity *= 2
        dato = Reallocate(dato, capacity * Sizeof(Integer))
    End If
    
    rear = (rear + 1) Mod capacity
    dato[rear] = item
    size += 1
End Sub

Function Queue.dequeue() As Integer
    If isEmpty() Then Return -1 ' Error: queue is empty
    
    Dim item As Integer = dato[front]
    front = (front + 1) Mod capacity
    size -= 1
    Return item
End Function

Function Queue.isEmpty() As Boolean
    Return (size = 0)
End Function

' HKGraph implementation
Constructor HKGraph(m As Integer, n As Integer)
    This.m = m
    This.n = n
    
    Dim As Integer i
    ' Initialize adjacency lists
    adj = Allocate((m + 1) * Sizeof(Integer Ptr))
    adj_size = Allocate((m + 1) * Sizeof(Integer))
    adj_capacity = Allocate((m + 1) * Sizeof(Integer))
    
    For i = 0 To m
        adj_size[i] = 0
        adj_capacity[i] = 10 ' Initial capacity
        adj[i] = Allocate(adj_capacity[i] * Sizeof(Integer))
    Next
    
    ' Initialize matching pairs
    pair_u = Allocate((m + 1) * Sizeof(Integer))
    pair_v = Allocate((n + 1) * Sizeof(Integer))
    
    ' Initialize distance array
    dist = Allocate((m + 1) * Sizeof(Double))
    
    ' Set initial values
    For i = 0 To m
        pair_u[i] = NIL
    Next
    
    For i = 0 To n
        pair_v[i] = NIL
    Next
End Constructor

Destructor HKGraph()
    ' Free all allocated memory
    For i As Integer = 0 To m
        If adj[i] <> NULL Then Deallocate(adj[i])
    Next
    
    If adj <> NULL Then Deallocate(adj)
    
    If adj_size <> NULL Then Deallocate(adj_size)
    
    If adj_capacity <> NULL Then Deallocate(adj_capacity)
    
    If pair_u <> NULL Then Deallocate(pair_u)
    
    If pair_v <> NULL Then Deallocate(pair_v)
    
    If dist <> NULL Then Deallocate(dist)
End Destructor

Sub HKGraph.add_edge(u As Integer, v As Integer)
    ' Ensure vertices are within the valid range
    If u >= 1 Andalso u <= m Andalso v >= 1 Andalso v <= n Then
        ' Check if we need to resize the adjacency list
        If adj_size[u] = adj_capacity[u] Then
            adj_capacity[u] *= 2
            adj[u] = Reallocate(adj[u], adj_capacity[u] * Sizeof(Integer))
        End If
        
        ' Add v to u's adjacency list
        adj[u][adj_size[u]] = v
        adj_size[u] += 1
    End If
End Sub

Function HKGraph.bfs() As Boolean
    Dim queue As Queue
    Dim As Integer u, i, v
    
    ' Initialize distances for vertices in U
    For u = 1 To m
        If pair_u[u] = NIL Then
            ' If u is a free vertex, its distance is 0, add to queue
            dist[u] = 0
            queue.enqueue(u)
        Else
            ' Otherwise, set distance to infinity initially
            dist[u] = INF
        End If
    Next
    
    ' Distance to the NIL node represents the length of the shortest augmenting path
    dist[NIL] = INF
    
    While Not queue.isEmpty()
        u = queue.dequeue()
        
        ' If the path through u can potentially lead to a shorter augmenting path
        If dist[u] < dist[NIL] Then
            ' Explore neighbors v of u in V
            For i = 0 To adj_size[u] - 1
                v = adj[u][i]
                Dim matched_u As Integer = pair_v[v] ' Get the vertex u' matched with v
                
                ' If the matched vertex u' hasn't been visited yet (its distance is INF)
                If dist[matched_u] = INF Then
                    ' Set the distance of u' based on u
                    dist[matched_u] = dist[u] + 1
                    ' Enqueue u' to explore further
                    queue.enqueue(matched_u)
                End If
            Next
        End If
    Wend
    
    ' If dist[NIL] is still INF, no augmenting path was found
    Return dist[NIL] <> INF
End Function

Function HKGraph.dfs(u As Integer) As Boolean
    If u <> NIL Then
        ' Explore neighbors v of u in V
        For i As Integer = 0 To adj_size[u] - 1
            Dim v As Integer = adj[u][i]
            Dim matched_u As Integer = pair_v[v] ' Get the vertex u' matched with v
            
            ' Check if the edge (u, v) leads to a vertex u'
            ' such that the path u -> v -> u' is part of a shortest augmenting path
            If dist[matched_u] = dist[u] + 1 Then
                ' Recursively call DFS on u'
                If dfs(matched_u) Then
                    ' If an augmenting path is found starting from u',
                    ' update the matching: match v with u, and u with v
                    pair_v[v] = u
                    pair_u[u] = v
                    Return True ' Augmentation successful
                End If
            End If
        Next
        
        ' If no augmenting path was found starting from u through any neighbor v,
        ' mark u as visited in this DFS phase by setting its distance to INF
        dist[u] = INF
        Return False ' Augmentation failed for this path
    End If
    
    ' Base case: If u is NIL, it means we have reached the end of an alternating path
    Return True
End Function

Function HKGraph.hopcroft_karp_algorithm() As Integer
    Dim As Integer i
    ' Initialize matching pairs to NIL (unmatched)
    For i = 0 To m
        pair_u[i] = NIL
    Next
    
    For i = 0 To n
        pair_v[i] = NIL
    Next
    
    Dim matching_size As Integer = 0 ' Initialize the size of the matching
    
    ' Keep finding augmenting paths using BFS and DFS until no more exist
    While bfs()
        ' For every free vertex u in U
        For i = 1 To m
            ' If i is free and an augmenting path starting from i is found via DFS
            If pair_u[i] = NIL Andalso dfs(i) Then matching_size += 1 ' Increment the matching size
        Next
    Wend
    
    Return matching_size
End Function

' --- Testing ---
Sub runTests()
    ' Runs test cases for the Hopcroft-Karp implementation.
    Print "Running tests..."
    
    Dim As Integer result, expected, i, j
    
    ' Test Case 1: Simple graph with one edge
    ' m=3, n=5, edges = [(1, 4)] - Expected matching size = 1
    Print "Test 1: ";
    Dim g1 As HKGraph = HKGraph(3, 5)
    g1.add_edge(1, 4)
    
    result = g1.hopcroft_karp_algorithm()
    expected = 1
    
    Print "Result=" & result; ", Expected=" & expected
    'Print "Test 1"; Iif(result = expected, "Passed", "Failed")
    If result = expected Then
        'Print "Test 1 Passed"
        success = success + 1
    Else
        Print "Test 1 Failed"
    End If
    
    ' Test Case 2: Graph with multiple edges
    ' m=6, n=6, edges = [(1,4), (1,5), (5,1)]
    ' Expected matching size = 2 (e.g., (1,4), (5,1) or (1,5), (5,1))
    
    Print "Test 2: ";
    Dim g2 As HKGraph = HKGraph(6, 6)
    g2.add_edge(1, 4)
    g2.add_edge(1, 5)
    g2.add_edge(5, 1)
    
    result = g2.hopcroft_karp_algorithm()
    expected = 2
    
    Print "Result=" & result; ", Expected=" & expected
    If result = expected Then
        'Print "Test 2 Passed"
        success = success + 1
    Else
        Print "Test 2 Failed"
    End If
    
    ' Test Case 3: Complete Bipartite Graph K_{3,3}
    ' m=3, n=3, all possible edges. Expected matching size = 3
    Print "Test 3: ";
    Dim g3 As HKGraph = HKGraph(3, 3)
    
    For i = 1 To 3
        For j = 1 To 3
            g3.add_edge(i, j)
        Next
    Next
    
    result = g3.hopcroft_karp_algorithm()
    expected = 3
    
    Print "Result=" & result; ", Expected=" & expected
    If result = expected Then
        'Print "Test 3 Passed"
        success = success + 1
    Else
        Print "Test 3 Failed"
    End If
    
    ' Test Case 4: No edges
    ' m=2, n=2, no edges. Expected matching size = 0
    Print "Test 4: ";
    Dim g4 As HKGraph = HKGraph(2, 2)
    
    result = g4.hopcroft_karp_algorithm()
    expected = 0
    
    Print "Result=" & result; ", Expected=" & expected
    If result = expected Then
        'Print "Test 4 Passed"
        success = success + 1
    Else
        Print "Test 4 Failed"
    End If
    
    If success = 4 Then Print "All tests completed!"
End Sub

Sub main()
    ' Run self-tests first
    runTests()
    
    Print !"\n--- Running main execution with hard-coded input ---"
    
    ' --- Hard-coded input data ---
    Dim v1 As Integer = 4 ' Number of vertices in left partition (m)
    Dim v2 As Integer = 4 ' Number of vertices in right partition (n)
    
    ' Create the graph object
    Dim g As HKGraph = HKGraph(v1, v2)
    
    ' Hard-coded edges
    Dim edges(5, 1) As Integer = { {1, 1}, {1, 3}, {2, 3}, {3, 4}, {4, 3}, {4, 2} }
    
    Dim e As Integer = 6 ' Number of edges
    
    Print "Hard-coded graph dimensions: m=" & v1 & ", n=" & v2 & ", edges=" & e
    Print "Adding hard-coded edges:"
    
    ' Add edges from the hard-coded list
    For i As Integer = 0 To e - 1
        Dim u As Integer = edges(i, 0)
        Dim v As Integer = edges(i, 1)
        
        Print "  Adding edge: (" & u & ", " & v & ")"
        
        ' Add edge only if vertices are within valid 1-based range
        If u >= 1 Andalso u <= v1 Andalso v >= 1 Andalso v <= v2 Then
            g.add_edge(u, v)
        Else
            ' This warning is important if hardcoded data might be invalid
            Print "Warning: Skipping invalid hard-coded edge (" & u & ", " & v & ") - indices out of range [1.." & v1 & "] or [1.." & v2 & "]"
        End If
    Next
    
    ' Run the Hopcroft-Karp algorithm
    Dim max_matching_size As Integer = g.hopcroft_karp_algorithm()
    
    ' Print the result
    Print !"\nMaximum matching size is"; max_matching_size
End Sub

main()

Sleep
Output:
Running tests...
Test 1: Result=1, Expected=1
Test 2: Result=2, Expected=2
Test 3: Result=3, Expected=3
Test 4: Result=0, Expected=0
All tests completed!

--- Running main execution with hard-coded input ---
Hard-coded graph dimensions: m=4, n=4, edges=6
Adding hard-coded edges:
  Adding edge: (1, 1)
  Adding edge: (1, 3)
  Adding edge: (2, 3)
  Adding edge: (3, 4)
  Adding edge: (4, 3)
  Adding edge: (4, 2)

Maximum matching size is 4


Translation of: Python
package main

import (
	"container/list"
	"fmt"
	"math"
)

const (
	NIL = 0
)

type HKGraph struct {
	m, n    int
	adj     [][]int
	pairU   []int
	pairV   []int
	dist    []float64
}

func NewHKGraph(m, n int) *HKGraph {
	adj := make([][]int, m+1)
	for i := range adj {
		adj[i] = make([]int, 0)
	}
	
	return &HKGraph{
		m:     m,
		n:     n,
		adj:   adj,
		pairU: make([]int, m+1),
		pairV: make([]int, n+1),
		dist:  make([]float64, m+1),
	}
}

func (g *HKGraph) AddEdge(u, v int) {
	if 1 <= u && u <= g.m && 1 <= v && v <= g.n {
		g.adj[u] = append(g.adj[u], v)
	}
}

func (g *HKGraph) BFS() bool {
	queue := list.New()
	
	for u := 1; u <= g.m; u++ {
		if g.pairU[u] == NIL {
			g.dist[u] = 0
			queue.PushBack(u)
		} else {
			g.dist[u] = math.Inf(1)
		}
	}
	
	g.dist[NIL] = math.Inf(1)
	
	for queue.Len() > 0 {
		u := queue.Remove(queue.Front()).(int)
		
		if g.dist[u] < g.dist[NIL] {
			for _, v := range g.adj[u] {
				matchedU := g.pairV[v]
				if math.IsInf(g.dist[matchedU], 1) {
					g.dist[matchedU] = g.dist[u] + 1
					queue.PushBack(matchedU)
				}
			}
		}
	}
	
	return !math.IsInf(g.dist[NIL], 1)
}

func (g *HKGraph) DFS(u int) bool {
	if u != NIL {
		for _, v := range g.adj[u] {
			matchedU := g.pairV[v]
			if g.dist[matchedU] == g.dist[u]+1 {
				if g.DFS(matchedU) {
					g.pairV[v] = u
					g.pairU[u] = v
					return true
				}
			}
		}
		
		g.dist[u] = math.Inf(1)
		return false
	}
	
	return true
}

func (g *HKGraph) HopcroftKarpAlgorithm() int {
	g.pairU = make([]int, g.m+1)
	g.pairV = make([]int, g.n+1)
	matchingSize := 0
	
	for g.BFS() {
		for u := 1; u <= g.m; u++ {
			if g.pairU[u] == NIL && g.DFS(u) {
				matchingSize++
			}
		}
	}
	
	return matchingSize
}

func runTests() {
	fmt.Println("Running tests...")
	
	g1 := NewHKGraph(3, 5)
	g1.AddEdge(1, 4)
	res1 := g1.HopcroftKarpAlgorithm()
	expectedRes1 := 1
	fmt.Printf("Test 1: Result=%d, Expected=%d\n", res1, expectedRes1)
	if res1 != expectedRes1 {
		panic(fmt.Sprintf("Test 1 Failed: Expected %d, got %d", expectedRes1, res1))
	}
	
	g2 := NewHKGraph(6, 6)
	g2.AddEdge(1, 4)
	g2.AddEdge(1, 5)
	g2.AddEdge(5, 1)
	res2 := g2.HopcroftKarpAlgorithm()
	expectedRes2 := 2
	fmt.Printf("Test 2: Result=%d, Expected=%d\n", res2, expectedRes2)
	if res2 != expectedRes2 {
		panic(fmt.Sprintf("Test 2 Failed: Expected %d, got %d", expectedRes2, res2))
	}
	
	g3 := NewHKGraph(3, 3)
	for i := 1; i <= 3; i++ {
		for j := 1; j <= 3; j++ {
			g3.AddEdge(i, j)
		}
	}
	res3 := g3.HopcroftKarpAlgorithm()
	expectedRes3 := 3
	fmt.Printf("Test 3: Result=%d, Expected=%d\n", res3, expectedRes3)
	if res3 != expectedRes3 {
		panic(fmt.Sprintf("Test 3 Failed: Expected %d, got %d", expectedRes3, res3))
	}
	
	g4 := NewHKGraph(2, 2)
	res4 := g4.HopcroftKarpAlgorithm()
	expectedRes4 := 0
	fmt.Printf("Test 4: Result=%d, Expected=%d\n", res4, expectedRes4)
	if res4 != expectedRes4 {
		panic(fmt.Sprintf("Test 4 Failed: Expected %d, got %d", expectedRes4, res4))
	}
	
	fmt.Println("All tests passed!")
}

func main() {
	runTests()
	
	fmt.Println("\n--- Running main execution with hard-coded input ---")
	
	hardcodedV1 := 4
	hardcodedV2 := 4
	hardcodedEdges := [][2]int{
		{1, 1},
		{1, 3},
		{2, 3},
		{3, 4},
		{4, 3},
		{4, 2},
	}
	
	v1 := hardcodedV1
	v2 := hardcodedV2
	edgesData := hardcodedEdges
	e := len(edgesData)
	
	g := NewHKGraph(v1, v2)
	fmt.Printf("Hard-coded graph dimensions: m=%d, n=%d, edges=%d\n", v1, v2, e)
	fmt.Println("Adding hard-coded edges:")
	
	for _, edge := range edgesData {
		u, v := edge[0], edge[1]
		fmt.Printf("  Adding edge: (%d, %d)\n", u, v)
		
		if 1 <= u && u <= v1 && 1 <= v && v <= v2 {
			g.AddEdge(u, v)
		} else {
			fmt.Printf("Warning: Skipping invalid hard-coded edge (%d, %d) - indices out of range [1..%d] or [1..%d]\n", 
				u, v, v1, v2)
		}
	}
	
	maxMatchingSize := g.HopcroftKarpAlgorithm()
	fmt.Printf("\nMaximum matching size is %d\n", maxMatchingSize)
}
Output:
Running tests...
Test 1: Result=1, Expected=1
Test 2: Result=2, Expected=2
Test 3: Result=3, Expected=3
Test 4: Result=0, Expected=0
All tests passed!

--- Running main execution with hard-coded input ---
Hard-coded graph dimensions: m=4, n=4, edges=6
Adding hard-coded edges:
  Adding edge: (1, 1)
  Adding edge: (1, 3)
  Adding edge: (2, 3)
  Adding edge: (3, 4)
  Adding edge: (4, 3)
  Adding edge: (4, 2)

Maximum matching size is 4


import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public final class HopcroftKarpAlgorithm {

	public static void main(String[] args) {		
		System.out.println("Running tests:");
	    int successCount = 0;

	    // Test Case 1
	    successCount = testValue(1, 3, 5, List.of( new Edge(1, 4) ), 1);
	    
	    // Test Case 2
	    successCount += testValue(2, 6, 6, List.of( new Edge(1, 4), new Edge(1, 5), new Edge(5, 1) ), 2);
	    
	    // Test Case 3: Complete Bipartite Graph K(3, 3)
	    List<Edge> edges = new ArrayList<Edge>();
	    for ( int i = 1; i <= 3; i++ ) {
	        for ( int j = 1; j <= 3; j++ ) {
	        	edges.addLast( new Edge(i, j) );
	        }
	    }
	    successCount += testValue(3, 3, 3, edges, 3);
	    
	    // Test Case 4: No edges
	    successCount += testValue(4, 2, 2, List.of(), 0);
	    
	    // Test Case 5
	    edges = List.of(
	    	new Edge(1, 1), new Edge(1, 3), new Edge(2, 3), new Edge(3, 4), new Edge(4, 3), new Edge(4, 2) );
	    successCount += testValue(5, 4, 4, edges, 4);

	    if ( successCount == 5 ) {
	    	System.out.println("All tests passed.");
	    }
	}	
	
	private static int testValue(int testNumber, int m, int n, List<Edge> edges, int expectedResult) {
		BipartiteGraph graph = new BipartiteGraph(m, n);
	    edges.forEach( edge -> graph.addEdge(edge.from, edge.to) );
	    final int result = graph.hopcroftKarpAlgorithm();
	    System.out.println("Test " + testNumber + ": Result = " + result + ", Expected = " + expectedResult);
	    if ( result == expectedResult ) {
	    	return 1;
	    }
	    
	    System.out.println("Test " + testNumber + " failed.");	
	    return 0;
	}
	
	private record Edge(int from, int to) {}

}

/**
 * Representation of a bipartite graph.
 * Vertices in the left partition, U, are numbered from 1 to m,
 * and vertices in the right partition, V, are numbered 1 to n.
 */
final class BipartiteGraph {
	
	public BipartiteGraph(int aM, int aN) {
		m = aM;
		n = aN;
		
		adjacencyLists = IntStream.range(0, m + 1).boxed()
				                  .map( i -> new ArrayList<Integer>() ).collect(Collectors.toList());
		pairU = Stream.generate( () -> NIL ).limit(m + 1).collect(Collectors.toList());
		pairV = Stream.generate( () -> NIL ).limit(n + 1).collect(Collectors.toList());
		levels = Stream.generate( () -> INFINITY ).limit(m + 1).collect(Collectors.toList());
	}
	
	public void addEdge(int u, int v) {
        if ( 1 <= u && u <= m && 1 <= v && v <= n ) {
            adjacencyLists.get(u).addLast(v);
        } else {
            throw new AssertionError("Attempt to add an edge (" + u + ", " + v + ") which is out of bounds");
        }
    }
	
	/**
	 * Return the matching size of the bipartite graph.
	 */
    public int hopcroftKarpAlgorithm() {
        pairU = Stream.generate( () -> NIL ).limit(m + 1).collect(Collectors.toList());
		pairV = Stream.generate( () -> NIL ).limit(n + 1).collect(Collectors.toList());
        int matchingSize = 0;

        while ( breadthFirstSearch() ) {
            for ( int u = 1; u <= m; u++ ) {
                if ( pairU.get(u) == NIL && depthFirstSearch(u) ) { // vertex u is free and an augmenting path starting                	                           
                    matchingSize += 1;                              // from u has been found by the depth first search
                }
            }
        }
        return matchingSize;
    }
	
	/**
     * Determines whether there exists an augmenting path starting from a free vertex in U.
     * 
     * Return true if an augmenting path could exist, otherwise false.
     */
    private boolean breadthFirstSearch() {
        Deque<Integer> queue = new ArrayDeque<Integer>();        
        for ( int u = 1; u <= m; u++ ) { // Initialise 'levels' for the vertices in U
            if ( pairU.get(u) == NIL ) { // If u is a free vertex, its level is 0 add it is added to the queue               
                levels.set(u, 0);
                queue.offerLast(u);
            } else { // Otherwise, set 'levels' to infinity                
                levels.set(u, INFINITY);
            }
        }
        
        // The 'level' to the NIL node represents the length of the shortest augmenting path
        levels.set(NIL, INFINITY);

        while ( ! queue.isEmpty() ) {
            final int u = queue.pollFirst();           
            if ( levels.get(u) < levels.get(NIL) ) { // The path through u could lead to a shorter augmenting path               
                for ( int v : adjacencyLists.get(u) ) { // Explore the neighbours v of u in V
                    final int matchedU = pairV.get(v);                    
                    if ( levels.get(matchedU) == INFINITY ) { // The matched vertex has not been visited yet
                       levels.set(matchedU, levels.get(u) + 1);
                        queue.offerLast(matchedU); // Enqueue the matched vertex to explore it further
                    }
                }
            }
        }
        
        // An augmenting path from the initial free vertices was found if levels.get(NIL) is not INFINITY
        return levels.get(NIL) != INFINITY;        
    }
    
    /**
     * Determine whether the shortest path from vertex u in U found by breadthFirstSearch() can be augmented.
     *
     * Return true if an augmenting path was found starting from u, otherwise false.
     */
    private boolean depthFirstSearch(int u) {
        if ( u != NIL ) {            
            for ( int v : adjacencyLists.get(u) ) { // Explore neighbours v of u in V
                final int matchedU = pairV.get(v);
                // Check whether the edge (u, v) leads to a vertex matchedU
                // such that the path u -> v -> matchedU is part of a shortest augmenting path
                if ( levels.get(matchedU) == levels.get(u) + 1 ) {
                    if ( depthFirstSearch(matchedU) ) { // An augmenting path is found starting from 'matchedU'
                        pairV.set(v, u); // Match v with u,
                        pairU.set(u, v); // and u with v
                        return true;
                    }
                }
            }
            
            // No augmenting path was found starting from vertex u through any of its neighbours v,
            // so remove u from the depth first search phase of the algorithm
            levels.set(u, INFINITY);
            return false;          
        }
        
        return true;        
    }
	
	private List<List<Integer>> adjacencyLists; // adjacencyLists(u) stores a list of neighbours of u in V
	private List<Integer> pairU; // pairU(u) stores the vertex v in V matched with u in U, or NIL if unmatched
	private List<Integer> pairV; // pairV(v) stores the vertex u in U matched with v in V, or NIL if unmatched
	private List<Integer> levels; // levels(u) stores the level of vertex u in U during a breadth first search
	
	private final int m; // Index of the vertices in the left partition
	private final int n; // Index of the vertices in the right partition
	
	private static final int NIL = 0;
	private static final int INFINITY = Integer.MAX_VALUE;
	
}
Output:
Running tests:
Test 1: Result = 1, Expected = 1
Test 2: Result = 2, Expected = 2
Test 3: Result = 3, Expected = 3
Test 4: Result = 0, Expected = 0
Test 5: Result = 4, Expected = 4
All tests passed.


Translation of: Python
class HKGraph {
    /**
     * Implementation of the Hopcroft-Karp algorithm for finding maximum matching
     * in a bipartite graph.
     *
     * Assumes vertices in the left partition (U) are numbered 1 to m,
     * and vertices in the right partition (V) are numbered 1 to n.
     * The NIL node is represented by 0.
     */

    /**
     * Constructor for the HKGraph class.
     *
     * @param {number} m Number of vertices in the left partition (U).
     * @param {number} n Number of vertices in the right partition (V).
     */
    constructor(m, n) {
        this.m = m; // Number of vertices on the left side (U)
        this.n = n; // Number of vertices on the right side (V)

        // Adjacency list: adj[u] contains list of neighbors of u in V
        // Initialize with empty lists for vertices 1 to m
        this.adj = Array(m + 1).fill(null).map(() => []);

        // Matching pairs:
        // pair_u[u] stores the vertex v in V matched with u in U (or NIL if unmatched)
        this.pair_u = Array(m + 1).fill(0);
        // pair_v[v] stores the vertex u in U matched with v in V (or NIL if unmatched)
        this.pair_v = Array(n + 1).fill(0);

        // dist[u] stores the distance (level) of vertex u in U during BFS
        // Initialized within the hopcroft_karp_algorithm or bfs method
        this.dist = Array(m + 1).fill(Infinity);
    }

    /**
     * Adds a directed edge from vertex u (left partition) to vertex v (right partition).
     *
     * @param {number} u Vertex index in the left partition (1 to m).
     * @param {number} v Vertex index in the right partition (1 to n).
     */
    addEdge(u, v) {
        // Ensure vertices are within the valid range
        if (1 <= u && u <= this.m && 1 <= v && v <= this.n) {
            this.adj[u].push(v); // Add v to u's adjacency list
        } else {
            // Optionally print a warning for edges added outside the defined range
            // This check is now also done in the main section when adding edges.
            //console.warn(`Warning: Attempted to add edge (${u}, ${v}) outside graph bounds [1..${this.m}], [1..${this.n}]`);
            //pass
        }
    }

    /**
     * Performs Breadth-First Search (BFS) to find layers in the graph.
     * It checks if there exists an augmenting path starting from a free vertex in U.
     *
     * @returns {boolean} True if an augmenting path might exist (dist[NIL] is finite),
     *                  False otherwise.
     */
    bfs() {
        const queue = []; // Use array as queue

        // Initialize distances for vertices in U
        for (let u = 1; u <= this.m; u++) {
            if (this.pair_u[u] === 0) {
                // If u is a free vertex, its distance is 0, add to queue
                this.dist[u] = 0;
                queue.push(u);
            } else {
                // Otherwise, set distance to infinity initially
                this.dist[u] = Infinity;
            }
        }

        // Distance to the NIL node represents the length of the shortest augmenting path
        this.dist[0] = Infinity;

        while (queue.length > 0) {
            const u = queue.shift(); // Dequeue a vertex from U

            // If the path through u can potentially lead to a shorter augmenting path
            if (this.dist[u] < this.dist[0]) {
                // Explore neighbors v of u in V
                for (const v of this.adj[u]) {
                    const matched_u = this.pair_v[v]; // Get the vertex u' matched with v
                    // If the matched vertex u' hasn't been visited yet (its distance is INF)
                    if (this.dist[matched_u] === Infinity) {
                        // Set the distance of u' based on u
                        this.dist[matched_u] = this.dist[u] + 1;
                        // Enqueue u' to explore further
                        queue.push(matched_u);
                    }
                }
            }
        }

        // If dist[NIL] is still INF, no augmenting path was found originating
        // from the initial free vertices. Otherwise, augmenting paths might exist.
        return this.dist[0] !== Infinity;
    }

    /**
     * Performs Depth-First Search (DFS) starting from vertex u in U
     * to find and augment along a shortest path identified by BFS.
     *
     * @param {number} u The current vertex in U being visited (or NIL).
     *
     * @returns {boolean} True if an augmenting path was found and used starting from u,
     *                  False otherwise.
     */
    dfs(u) {
        if (u !== 0) {
            // Explore neighbors v of u in V
            for (const v of this.adj[u]) {
                const matched_u = this.pair_v[v]; // Get the vertex u' matched with v
                // Check if the edge (u, v) leads to a vertex u'
                // such that the path u -> v -> u' is part of a shortest augmenting path
                if (this.dist[matched_u] === this.dist[u] + 1) {
                    // Recursively call DFS on u'
                    if (this.dfs(matched_u)) {
                        // If an augmenting path is found starting from u',
                        // update the matching: match v with u, and u with v
                        this.pair_v[v] = u;
                        this.pair_u[u] = v;
                        return true; // Augmentation successful
                    }
                }
            }

            // If no augmenting path was found starting from u through any neighbor v,
            // mark u as visited in this DFS phase by setting its distance to INF
            this.dist[u] = Infinity;
            return false; // Augmentation failed for this path
        }

        // Base case: If u is NIL, it means we have reached the end of an alternating path
        // originating from a free vertex in U and ending at a free vertex in V (represented by NIL).
        return true;
    }

    /**
     * Executes the Hopcroft-Karp algorithm to find the maximum matching.
     *
     * @returns {number} The size of the maximum matching found.
     */
    hopcroftKarpAlgorithm() {
        // Initialize matching pairs to NIL (unmatched)
        this.pair_u = Array(this.m + 1).fill(0);
        this.pair_v = Array(this.n + 1).fill(0);

        let matching_size = 0; // Initialize the size of the matching

        // Keep finding augmenting paths using BFS and DFS until no more exist
        while (this.bfs()) {
            // For every free vertex u in U
            for (let u = 1; u <= this.m; u++) {
                // If u is free and an augmenting path starting from u is found via DFS
                if (this.pair_u[u] === 0 && this.dfs(u)) {
                    // Increment the matching size
                    matching_size++;
                }
            }
        }

        return matching_size;
    }
}


// --- Testing ---

function tests() {
    /** Runs test cases for the Hopcroft-Karp implementation. */
    console.log("Running tests...");

    // Test Case 1 (Corrected from C++ version - using 1-based indexing)
    // m=3, n=5, edges = [(1, 4)] - Expected matching size = 1
    let g1 = new HKGraph(3, 5);
    g1.addEdge(1, 4);
    let res1 = g1.hopcroftKarpAlgorithm();
    let expected_res1 = 1;
    console.log(`Test 1: Result=${res1}, Expected=${expected_res1}`);
    console.assert(res1 === expected_res1, `Test 1 Failed: Expected ${expected_res1}, got ${res1}`);

    // Test Case 2 (Corrected from C++ version - using 1-based indexing, assuming (5,0) meant (5,1) or similar valid edge)
    // m=6, n=6, edges = [(1,4), (1,5), (5,1)]
    // Expected matching size = 2 (e.g., (1,4), (5,1) or (1,5), (5,1))
    // Note: Original C++ test had (0,1) and (5,0) which are problematic with 1-based index logic.
    let g2 = new HKGraph(6, 6);
    // g3.addEdge(0,1); // Invalid vertex 0 in U
    g2.addEdge(1, 4);
    g2.addEdge(1, 5);
    g2.addEdge(5, 1); // Assuming (5,0) meant a valid edge like (5,1)
    // g2.addEdge(5, 0); // Invalid vertex 0 in V
    let res2 = g2.hopcroftKarpAlgorithm();
    let expected_res2 = 2;
    console.log(`Test 2: Result=${res2}, Expected=${expected_res2}`);
    console.assert(res2 === expected_res2, `Test 3 Failed: Expected ${expected_res2}, got ${res2}`);

    // Test Case 3: Complete Bipartite Graph K_{3,3}
    // m=3, n=3, all possible edges. Expected matching size = 3
    let g3 = new HKGraph(3, 3);
    for (let i = 1; i <= 3; i++) {
        for (let j = 1; j <= 3; j++) {
            g3.addEdge(i, j);
        }
    }
    let res3 = g3.hopcroftKarpAlgorithm();
    let expected_res3 = 3;
    console.log(`Test 3: Result=${res3}, Expected=${expected_res3}`);
    console.assert(res3 === expected_res3, `Test 4 Failed: Expected ${expected_res3}, got ${res3}`);

    // Test Case 4: No edges
    // m=2, n=2, no edges. Expected matching size = 0
    let g4 = new HKGraph(2, 2);
    let res4 = g4.hopcroftKarpAlgorithm();
    let expected_res4 = 0;
    console.log(`Test 4: Result=${res4}, Expected=${expected_res4}`);
    console.assert(res4 === expected_res4, `Test 4 Failed: Expected ${expected_res4}, got ${res4}`);

    console.log("All tests passed!");
}


// --- Main execution ---

// Check if running in a Node.js environment
if (typeof window === 'undefined') {
    // Run self-tests first
    tests();
    console.log("\n--- Running main execution with hard-coded input ---");

    // --- Hard-coded input data ---
    // Example 1: Corresponds to Test Case 2
    let hardcoded_v1 = 4; // Number of vertices in left partition (m)
    let hardcoded_v2 = 4; // Number of vertices in right partition (n)
    let hardcoded_edges = [
        [1, 1],
        [1, 3],
        [2, 3],
        [3, 4],
        [4, 3],
        [4, 2]
    ];
    // Expected output for Example 1: 3

    // Use the selected hardcoded data
    let v1 = hardcoded_v1;
    let v2 = hardcoded_v2;
    let edges_data = hardcoded_edges;
    let e = edges_data.length; // Number of edges is derived from the list

    // Create the graph object
    let g = new HKGraph(v1, v2);

    console.log(`Hard-coded graph dimensions: m=${v1}, n=${v2}, edges=${e}`);
    console.log("Adding hard-coded edges:");

    // Add edges from the hard-coded list
    for (const [u, v] of edges_data) {
        console.log(`  Adding edge: (${u}, ${v})`);
        // Add edge only if vertices are within valid 1-based range
        if (1 <= u && u <= v1 && 1 <= v && v <= v2) {
            g.addEdge(u, v);
        } else {
            // This warning is important if hardcoded data might be invalid
            console.warn(`Warning: Skipping invalid hard-coded edge (${u}, ${v}) - indices out of range [1..${v1}] or [1..${v2}]`);
        }
    }

    // Run the Hopcroft-Karp algorithm
    let max_matching_size = g.hopcroftKarpAlgorithm();

    // Print the result
    console.log(`\nMaximum matching size is ${max_matching_size}`);
}
Output:
Running tests...
Test 1: Result=1, Expected=1
Test 2: Result=2, Expected=2
Test 3: Result=3, Expected=3
Test 4: Result=0, Expected=0
All tests passed!

--- Running main execution with hard-coded input ---
Hard-coded graph dimensions: m=4, n=4, edges=6
Adding hard-coded edges:
  Adding edge: (1, 1)
  Adding edge: (1, 3)
  Adding edge: (2, 3)
  Adding edge: (3, 4)
  Adding edge: (4, 3)
  Adding edge: (4, 2)

Maximum matching size is 4
Translation of: Python
""" 
Note that because Julia's arrays are 1-based, the 1-based algorithm used 
in the original posting works more naturally under Julia as a 2-based algorithm.
Hard coded edges in the tests have been adjusted accordingly.
"""

using DataStructures

const NIL = 1
const INF = typemax(Int)

"""
Implementation of the Hopcroft-Karp algorithm for finding maximum matching
in a bipartite graph.

Assumes vertices in the left partition (U) are numbered 2 to m+1,
and vertices in the right partition (V) are numbered 2 to n+1.
The NIL node is represented by 1.
"""
mutable struct HKGraph
    m::Int
    n::Int
    adj::Vector{Vector{Int}}
    pair_u::Vector{Int}
    pair_v::Vector{Int}
    dist::Vector{Int}
end
"""
Constructor for the HKGraph class.
Arguments:
    m (int): Number of vertices in the left partition (U).
    n (int): Number of vertices in the right partition (V).
"""
function HKGraph(m, n)
    pair_u = fill(NIL, m + 1)
    pair_v = fill(NIL, n + 1)
    dist = fill(INF, m + 1)
    return HKGraph(m, n, [Int[] for _ in 1:m+1], pair_u, pair_v, dist)
end

"""
Adds a directed edge from vertex u (left partition) to vertex v (right partition).
Arguments:
    u (int): Vertex index in the left partition (2 to m+1).
    v (int): Vertex index in the right partition (2 to n+1).
"""
function add_edge!(g::HKGraph, u, v)
    # Ensure vertices are within the valid range
    if 2 <= u <= g.m+1 && 2 <= v <= g.n+1
        push!(g.adj[u], v)  # Add v to u's adjacency list
    else
        @warn "Attempted to add edge ({u}, {v}) outside graph bounds [2..$(g.m+1)], [2..$(g.n+1)]"
    end
end

"""
Performs Breadth-First Search (BFS) to find layers in the graph.
It checks if there exists an augmenting path starting from a free vertex in U.
Returns:
    bool: True if an augmenting path might exist (dist[NIL] is finite),
          False otherwise.
"""
function bfs(g::HKGraph)::Bool
    queue = Deque{Int}()
    # Initialize distances for vertices in U
    for u in 2:g.m+1
        if g.pair_u[u] == NIL
            # If u is a free vertex, its distance is 0, add to queue
            g.dist[u] = 0
            push!(queue, u)
        else
            # Otherwise, set distance to infinity initially
            g.dist[u] = INF
        end
    end
    # Distance to the NIL node represents the length of the shortest augmenting path
    g.dist[NIL] = INF
    while !isempty(queue)
        u = popfirst!(queue)  # Dequeue a vertex from U
        # If the path through u can potentially lead to a shorter augmenting path
        if g.dist[u] < g.dist[NIL]
            # Explore neighbors v of u in V
            for v in g.adj[u]
                matched_u = g.pair_v[v]  # Get the vertex u' matched with v
                # If the matched vertex u' hasn't been visited yet (its distance is INF)
                if g.dist[matched_u] == INF
                    # Set the distance of u' based on u
                    g.dist[matched_u] = g.dist[u] + 1
                    # Enqueue u' to explore further
                    push!(queue, matched_u)
                end
            end
        end
    end
    # INIL is still INF, no augmenting path was found originating
    # from the initial free vertices. Otherwise, augmenting paths might exist.
    return g.dist[NIL] != INF
end

"""
Performs Depth-First Search (DFS) starting from vertex u in U
to find and augment along a shortest path identified by BFS.
Arguments:
    u (int): The current vertex in U being visited (oNIL).
Returns:
    bool: True if an augmenting path was found and used starting from u,
          False otherwise.
"""
function dfs(g::HKGraph, u)::Bool
    if u != NIL
        # Explore neighbors v of u in V
        for v in g.adj[u]
            matched_u = g.pair_v[v]  # Get the vertex u' matched with v
            # Check if the edge (u, v) leads to a vertex u'
            # such that the path u -> v -> u' is part of a shortest augmenting path
            if g.dist[matched_u] == g.dist[u] + 1
                # Recursively call DFS on u'
                if dfs(g, matched_u)
                    # If an augmenting path is found starting from u',
                    # update the matching: match v with u, and u with v
                    g.pair_v[v] = u
                    g.pair_u[u] = v
                    return true  # Augmentation successful
                end
            end
        end
        # If no augmenting path was found starting from u through any neighbor v,
        # mark u as visited in this DFS phase by setting its distance to INF
        g.dist[u] = INF
        return false  # Augmentation failed for this path
    end
    # Base case: If u iNIL, it means we have reached the end of an alternating path
    # originating from a free vertex in U and ending at a free vertex in V (represented bNIL).
    return true
end

"""
Executes the Hopcroft-Karp algorithm to find the maximum matching.

Returns:
    int: The size of the maximum matching found.
"""
function hopcroft_karp_algorithm(g)::Int
    # Initialize matching pairs tNIL (unmatched)
    g.pair_u = fill(NIL, g.m + 1)
    g.pair_v = fill(NIL, g.n + 1)
    matching_size = 0  # Initialize the size of the matching

    # Keep finding augmenting paths using BFS and DFS until no more exist
    while bfs(g) 
        # For every free vertex u in U
        for u in 2:g.m+1
            # If u is free and an augmenting path starting from u is found via DFS
            if g.pair_u[u] == NIL && dfs(g, u)
                # Increment the matching size
                matching_size += 1
            end
        end
    end
    return matching_size
end

# --- Testing ---

""" Runs test cases for the Hopcroft-Karp implementation. """
function hk_g_tests()
    println("Running tests...")
    # Test Case 1 
    # m=3, n=5, edges = [(1+1, 4+1)] - 2 based indices, expected matching size = 1
    g1 = HKGraph(3, 5)
    add_edge!(g1, 1+1, 4+1)
    res1 = hopcroft_karp_algorithm(g1)
    expected_res1 = 1
    println("Test 1: Result=$res1, Expected=$expected_res1")
    @assert res1 == expected_res1 "Test 1 Failed: Expected $expected_res1, got $res1"

    # Test Case 2 (changed values to 2 based indices)
    # m=6, n=6, edges = [(1,4), (1,5), (5,1)]
    # Expected matching size = 2 (e.g., (1,4), (5,1) or (1,5), (5,1))
    # Note: Original C++ test had (0,1) and (5,0) which are problematic with 1-based index logic.
    g2 = HKGraph(6, 6)
    add_edge!(g2,1+1, 4+1)
    add_edge!(g2, 1+1, 5+1)
    add_edge!(g2, 5+1, 1+1)  # Assuming (5,0) meant a valid edge like (5,1)
    res2 = hopcroft_karp_algorithm(g2)
    expected_res2 = 2
    println("Test 2: Result=$res2, Expected=$expected_res2")
    @assert res2 == expected_res2 "Test 3 Failed: Expected $expected_res2, got $res2"

    # Test Case 3: Complete Bipartite Graph K_{3,3}
    # m=3, n=3, all possible edges. Expected matching size = 3
    g3 = HKGraph(3, 3)
    for i in 2:4, j in 2:4
        add_edge!(g3, i, j)
    end
    res3 = hopcroft_karp_algorithm(g3)
    expected_res3 = 3
    println("Test 3: Result=$res3, Expected=$expected_res3")
    @assert res3 == expected_res3 "Test 4 Failed: Expected $expected_res3, got $res3"
    
    # Test Case 4: No edges
    # m=2, n=2, no edges. Expected matching size = 0
    g4 = HKGraph(2, 2)
    res4 = hopcroft_karp_algorithm(g4)
    expected_res4 = 0
    println("Test 4: Result=$res4, Expected=$expected_res4")
    @assert res4 == expected_res4 "Test 4 Failed: Expected $expected_res4, got $res4"

    print("All tests passed!")
end

function all_hk_tests()
    # Run g-tests first
    hk_g_tests()
    println("\n--- Running main execution with hard-coded input ---")

    # --- Hard-coded input data ---
    # Example 1: Corresponds to Test Case 2
    hardcoded_v1 = 4  # Number of vertices in left partition (m)
    hardcoded_v2 = 4  # Number of vertices in right partition (n)
    hardcoded_edges = [
        (2, 2),
        (2, 4),
        (3, 4),
        (4, 5),
        (5, 4),
        (5, 3)
    ]
    # Expected output for Example 1: 3

    # Use the selected hardcoded data
    v1 = hardcoded_v1
    v2 = hardcoded_v2
    edges_data = hardcoded_edges
    e_len = length(edges_data)  # Number of edges is derived from the list

    # Create the graph object
    g = HKGraph(v1, v2)

    println("Hard-coded graph dimensions: m=$v1, n=$v2, edges=$e_len")
    println("Adding hard-coded edges:")

    # Add edges from the hard-coded list
    for (u, v) in edges_data
        println("  Adding edge: 2-based ($u, $v), 1-based ($(u-1), $(v-1)) ")
        # Add edge only if vertices are within valid 2-based range
        if 2 <= u <= v1+1 && 2 <= v <= v2+1
            add_edge!(g, u, v)
        else
            # This warning is important if hardcoded data might be invalid
            println("Warning: Skipping invalid hard-coded edge ($u, $v) - indices out of range [1..$v1] or [1..$v2]")
        end
    end
    # Run the Hopcroft-Karp algorithm
    max_matching_size = hopcroft_karp_algorithm(g)

    # Print the result
    println("Maximum matching size is $max_matching_size")
end

all_hk_tests()
Output:
Running tests...
Test 1: Result=1, Expected=1
Test 2: Result=2, Expected=2
Test 3: Result=3, Expected=3
Test 4: Result=0, Expected=0
All tests passed!
--- Running main execution with hard-coded input ---
Hard-coded graph dimensions: m=4, n=4, edges=6
Adding hard-coded edges:
  Adding edge: 2-based (2, 2), 1-based (1, 1)
  Adding edge: 2-based (2, 4), 1-based (1, 3)
  Adding edge: 2-based (3, 4), 1-based (2, 3)
  Adding edge: 2-based (4, 5), 1-based (3, 4)
  Adding edge: 2-based (5, 4), 1-based (4, 3)
  Adding edge: 2-based (5, 3), 1-based (4, 2)
Maximum matching size is 4


Works with: Kotlin version 1.3.31
Translation of: Java
import java.util.*

data class Edge(val from: Int, val to: Int)

class BipartiteGraph(private val m: Int, private val n: Int) {

    private var adjacencyLists: List<MutableList<Int>> = List(m + 1) { mutableListOf<Int>() }
    private var pairU: MutableList<Int> = MutableList(m + 1) { NIL }
    private var pairV: MutableList<Int> = MutableList(n + 1) { NIL }
    private var levels: MutableList<Int> = MutableList(m + 1) { INFINITY }

    fun addEdge(u: Int, v: Int) {
        require(u in 1..m && v in 1..n) { "Attempt to add an edge ($u, $v) which is out of bounds" }
        adjacencyLists[u].add(v)
    }

    fun hopcroftKarpAlgorithm(): Int {
        pairU = MutableList(m + 1) { NIL }
        pairV = MutableList(n + 1) { NIL }

        var matchingSize = 0

        while (breadthFirstSearch()) {
            for (u in 1..m) {
                if (pairU[u] == NIL && depthFirstSearch(u)) {
                    matchingSize++
                }
            }
        }
        return matchingSize
    }

    private fun breadthFirstSearch(): Boolean {
        val queue: Queue<Int> = ArrayDeque()

        for (u in 1..m) {
            if (pairU[u] == NIL) {
                levels[u] = 0
                queue.offer(u)
            } else {
                levels[u] = INFINITY
            }
        }

        levels[NIL] = INFINITY

        while (queue.isNotEmpty()) {
            val u = queue.poll()
            if (levels[u] < levels[NIL]) {
                for (v in adjacencyLists[u]) {
                    val matchedU = pairV[v]
                    if (levels[matchedU] == INFINITY) {
                        levels[matchedU] = levels[u] + 1
                        queue.offer(matchedU)
                    }
                }
            }
        }

        return levels[NIL] != INFINITY
    }

    private fun depthFirstSearch(u: Int): Boolean {
        if (u != NIL) {
            for (v in adjacencyLists[u]) {
                val matchedU = pairV[v]
                if (levels[matchedU] == levels[u] + 1) {
                    if (depthFirstSearch(matchedU)) {
                        pairV[v] = u
                        pairU[u] = v
                        return true
                    }
                }
            }
            levels[u] = INFINITY
            return false
        }
        return true
    }

    companion object {
        private const val NIL = 0
        private const val INFINITY = Int.MAX_VALUE
    }
}

fun testValue(testNumber: Int, m: Int, n: Int, edges: List<Edge>, expectedResult: Int): Int {
    val graph = BipartiteGraph(m, n)
    edges.forEach { edge -> graph.addEdge(edge.from, edge.to) }
    val result = graph.hopcroftKarpAlgorithm()
    println("Test $testNumber: Result = $result, Expected = $expectedResult")
    if (result == expectedResult) return 1
    println("Test $testNumber failed.")
    return 0
}

fun main() {
    println("Running tests:")
    var successCount = 0

    // Test Case 1
    successCount += testValue(1, 3, 5, listOf(Edge(1, 4)), 1)

    // Test Case 2
    successCount += testValue(2, 6, 6, listOf(Edge(1, 4), Edge(1, 5), Edge(5, 1)), 2)

    // Test Case 3: Complete Bipartite Graph K(3,3)
    val edges3 = mutableListOf<Edge>()
    for (i in 1..3) {
        for (j in 1..3) {
            edges3.add(Edge(i, j))
        }
    }
    successCount += testValue(3, 3, 3, edges3, 3)

    // Test Case 4: No edges
    successCount += testValue(4, 2, 2, emptyList(), 0)

    // Test Case 5
    val edges5 = listOf(
        Edge(1, 1), Edge(1, 3), Edge(2, 3),
        Edge(3, 4), Edge(4, 3), Edge(4, 2)
    )
    successCount += testValue(5, 4, 4, edges5, 4)

    if (successCount == 5) {
        println("All tests passed.")
    }
}
Output:
Running tests:
Test 1: Result = 1, Expected = 1
Test 2: Result = 2, Expected = 2
Test 3: Result = 3, Expected = 3
Test 4: Result = 0, Expected = 0
Test 5: Result = 4, Expected = 4
All tests passed.


Works with: Lua version 5.4
Translation of: Perl
#!/usr/bin/env lua

-- Representation of a bipartite graph
-- Vertices in the left partition, U, are numbered from 1 to m,
-- and vertices in the right partition, V, are numbered 1 to n.
local BipartiteGraph = {}
BipartiteGraph.__index = BipartiteGraph

function BipartiteGraph:new(m, n)
    local self = {
        m = m,
        n = n,
        adjacency_lists = {},
        pair_u = {},
        pair_v = {},
        levels = {},
        NIL = 0,
        INFINITY = 999999999
    }
    
    -- Initialize adjacency lists
    for u = 1, m do
        self.adjacency_lists[u] = {}
    end
    
    -- Initialize pairs
    for u = 1, m do
        self.pair_u[u] = self.NIL
    end
    for v = 1, n do
        self.pair_v[v] = self.NIL
    end
    
    -- Initialize levels
    for u = 1, m do
        self.levels[u] = self.INFINITY
    end
    
    setmetatable(self, BipartiteGraph)
    return self
end

function BipartiteGraph:add_edge(u, v)
    if u >= 1 and u <= self.m and v >= 1 and v <= self.n then
        table.insert(self.adjacency_lists[u], v)
    else
        error("Attempt to add an edge (" .. u .. ", " .. v .. ") which is out of bounds")
    end
end

-- Return the matching size of the bipartite graph
function BipartiteGraph:hopcroft_karp_algorithm()
    -- Reset pairs
    for u = 1, self.m do
        self.pair_u[u] = self.NIL
    end
    for v = 1, self.n do
        self.pair_v[v] = self.NIL
    end
    
    local matching_size = 0
    
    while self:breadth_first_search() do
        for u = 1, self.m do
            if self.pair_u[u] == self.NIL and self:depth_first_search(u) then
                -- vertex u is free and an augmenting path starting
                -- from u has been found by the depth first search
                matching_size = matching_size + 1
            end
        end
    end
    
    return matching_size
end

-- Determines whether there exists an augmenting path starting from a free vertex in U.
-- Return true if an augmenting path could exist, otherwise false.
function BipartiteGraph:breadth_first_search()
    local queue = {}
    
    -- Initialize 'levels' for the vertices in U
    for u = 1, self.m do
        if self.pair_u[u] == self.NIL then
            -- If u is a free vertex, its level is 0 and it is added to the queue
            self.levels[u] = 0
            table.insert(queue, u)
        else
            -- Otherwise, set 'levels' to infinity
            self.levels[u] = self.INFINITY
        end
    end
    
    -- The 'level' to the NIL node represents the length of the shortest augmenting path
    self.levels[self.NIL] = self.INFINITY
    
    local queue_front = 1
    local queue_back = #queue
    
    while queue_front <= queue_back do
        local u = queue[queue_front]
        queue_front = queue_front + 1
        
        if self.levels[u] < self.levels[self.NIL] then
            -- The path through u could lead to a shorter augmenting path
            for _, v in ipairs(self.adjacency_lists[u]) do
                -- Explore the neighbours v of u in V
                local matched_u = self.pair_v[v]
                if self.levels[matched_u] == self.INFINITY then
                    -- The matched vertex has not been visited yet
                    self.levels[matched_u] = self.levels[u] + 1
                    queue_back = queue_back + 1
                    queue[queue_back] = matched_u -- Enqueue the matched vertex to explore it further
                end
            end
        end
    end
    
    -- An augmenting path from the initial free vertices was found if levels[NIL] is not INFINITY
    return self.levels[self.NIL] ~= self.INFINITY
end

-- Determine whether the shortest path from vertex u in U found by breadth_first_search() can be augmented.
-- Return true if an augmenting path was found starting from u, otherwise false.
function BipartiteGraph:depth_first_search(u)
    if u ~= self.NIL then
        for _, v in ipairs(self.adjacency_lists[u]) do
            -- Explore neighbours v of u in V
            local matched_u = self.pair_v[v]
            -- Check whether the edge (u, v) leads to a vertex matched_u
            -- such that the path u -> v -> matched_u is part of a shortest augmenting path
            if self.levels[matched_u] == self.levels[u] + 1 then
                if self:depth_first_search(matched_u) then
                    -- An augmenting path is found starting from 'matched_u'
                    self.pair_v[v] = u -- Match v with u,
                    self.pair_u[u] = v -- and u with v
                    return true
                end
            end
        end
        
        -- No augmenting path was found starting from vertex u through any of its neighbours v,
        -- so remove u from the depth first search phase of the algorithm
        self.levels[u] = self.INFINITY
        return false
    end
    
    return true
end

function test_value(test_number, m, n, edges, expected_result)
    local graph = BipartiteGraph:new(m, n)
    
    for _, edge in ipairs(edges) do
        graph:add_edge(edge.from, edge.to)
    end
    
    local result = graph:hopcroft_karp_algorithm()
    print("Test " .. test_number .. ": Result = " .. result .. ", Expected = " .. expected_result)
    
    if result == expected_result then
        return 1
    end
    
    print("Test " .. test_number .. " failed.")
    return 0
end

-- Main execution
print("Running tests:")
local success_count = 0

-- Test Case 1
success_count = success_count + test_value(1, 3, 5, {{from = 1, to = 4}}, 1)

-- Test Case 2
success_count = success_count + test_value(2, 6, 6, {
    {from = 1, to = 4},
    {from = 1, to = 5},
    {from = 5, to = 1}
}, 2)

-- Test Case 3: Complete Bipartite Graph K(3, 3)
local edges = {}
for i = 1, 3 do
    for j = 1, 3 do
        table.insert(edges, {from = i, to = j})
    end
end
success_count = success_count + test_value(3, 3, 3, edges, 3)

-- Test Case 4: No edges
success_count = success_count + test_value(4, 2, 2, {}, 0)

-- Test Case 5
edges = {
    {from = 1, to = 1},
    {from = 1, to = 3},
    {from = 2, to = 3},
    {from = 3, to = 4},
    {from = 4, to = 3},
    {from = 4, to = 2}
}
success_count = success_count + test_value(5, 4, 4, edges, 4)

if success_count == 5 then
    print("All tests passed.")
end
Output:
Running tests:
Test 1: Result = 1, Expected = 1
Test 2: Result = 2, Expected = 2
Test 3: Result = 3, Expected = 3
Test 4: Result = 0, Expected = 0
Test 5: Result = 4, Expected = 4
All tests passed.


Translation of: Julia
Works with: Mathematica 12.0.1
(* ===================== Hopcroft-Karp in Mathematica ===================== *)
(* Uses indexed global symbols for mutable graph state, since Associations   *)
(* are immutable value types in Mathematica and cannot be mutated in-place.  *)

$NIL = 1;
$INF = Infinity;

(* ---------- Graph constructor ----------
   Stores graph data in global indexed symbols:
     hkM[id], hkN[id], hkAdj[id,u], hkPairU[id,u], hkPairV[id,v], hkDist[id,u]
   Returns a graph id (integer). *)

$hkNextId = 1;

HKGraph[m_, n_] := Module[{id = $hkNextId},
  $hkNextId++;
  hkM[id] = m;
  hkN[id] = n;
  Do[hkAdj[id, u] = {}, {u, 1, m + 1}];
  Do[hkPairU[id, u] = $NIL, {u, 1, m + 1}];
  Do[hkPairV[id, v] = $NIL, {v, 1, n + 1}];
  Do[hkDist[id, u] = $INF, {u, 1, m + 1}];
  id
]

(* ---------- Add edge (2-based indices) ---------- *)
AddEdge[id_, u_, v_] :=
  If[2 <= u <= hkM[id] + 1 && 2 <= v <= hkN[id] + 1,
    hkAdj[id, u] = Append[hkAdj[id, u], v],
    Print["Warning: Edge (", u, ",", v, ") out of bounds"]
  ]

(* ---------- BFS ---------- *)
BFS[id_] := Module[{queue = {}, u, v, mu},
  Do[
    If[hkPairU[id, u] == $NIL,
      hkDist[id, u] = 0; AppendTo[queue, u],
      hkDist[id, u] = $INF
    ],
    {u, 2, hkM[id] + 1}
  ];
  hkDist[id, $NIL] = $INF;
  While[queue =!= {},
    u = First[queue]; queue = Rest[queue];
    If[hkDist[id, u] < hkDist[id, $NIL],
      Do[
        mu = hkPairV[id, v];
        If[hkDist[id, mu] == $INF,
          hkDist[id, mu] = hkDist[id, u] + 1;
          AppendTo[queue, mu]
        ],
        {v, hkAdj[id, u]}
      ]
    ]
  ];
  hkDist[id, $NIL] =!= $INF
]

(* ---------- DFS ---------- *)
DFS[id_, u_] := Module[{mu, found = False},
  If[u == $NIL, Return[True]];
  Do[
    mu = hkPairV[id, v];
    If[hkDist[id, mu] == hkDist[id, u] + 1,
      If[DFS[id, mu],
        hkPairV[id, v] = u;
        hkPairU[id, u] = v;
        found = True;
        Break[]
      ]
    ],
    {v, hkAdj[id, u]}
  ];
  If[!found, hkDist[id, u] = $INF];
  found
]

(* ---------- Main algorithm ---------- *)
HopcroftKarp[id_] := Module[{matchingSize = 0},
  Do[hkPairU[id, u] = $NIL, {u, 1, hkM[id] + 1}];
  Do[hkPairV[id, v] = $NIL, {v, 1, hkN[id] + 1}];
  While[BFS[id],
    Do[
      If[hkPairU[id, u] == $NIL && DFS[id, u],
        matchingSize++
      ],
      {u, 2, hkM[id] + 1}
    ]
  ];
  matchingSize
]

(* ===================== Tests ===================== *)

Print["Running tests..."]

(* Test 1: single edge *)
g1 = HKGraph[3, 5];
AddEdge[g1, 2, 5];
res1 = HopcroftKarp[g1];
Print["Test 1: Result=", res1, ", Expected=1"];
Assert[res1 == 1, "Test 1 Failed"]

(* Test 2 *)
g2 = HKGraph[6, 6];
AddEdge[g2, 2, 5]; AddEdge[g2, 2, 6]; AddEdge[g2, 6, 2];
res2 = HopcroftKarp[g2];
Print["Test 2: Result=", res2, ", Expected=2"];
Assert[res2 == 2, "Test 2 Failed"]

(* Test 3: Complete bipartite K_{3,3} *)
g3 = HKGraph[3, 3];
Do[AddEdge[g3, i, j], {i, 2, 4}, {j, 2, 4}];
res3 = HopcroftKarp[g3];
Print["Test 3: Result=", res3, ", Expected=3"];
Assert[res3 == 3, "Test 3 Failed"]

(* Test 4: No edges *)
g4 = HKGraph[2, 2];
res4 = HopcroftKarp[g4];
Print["Test 4: Result=", res4, ", Expected=0"];
Assert[res4 == 0, "Test 4 Failed"]

Print["All tests passed!"]

(* ===================== Hard-coded main example ===================== *)

Print["\n--- Running main execution with hard-coded input ---"]

v1 = 4; v2 = 4;
hardcodedEdges = {{2,2},{2,4},{3,4},{4,5},{5,4},{5,3}};
elen = Length[hardcodedEdges];

g = HKGraph[v1, v2];
Print["Hard-coded graph dimensions: m=", v1, ", n=", v2, ", edges=", elen];
Print["Adding hard-coded edges:"];

Do[
  {u, v} = e;
  Print["  Adding edge: 2-based (", u, ", ", v, "), 1-based (", u-1, ", ", v-1, ") "];
  If[2 <= u <= v1+1 && 2 <= v <= v2+1,
    AddEdge[g, u, v],
    Print["Warning: Skipping invalid hard-coded edge (", u, ",", v, ") - indices out of range"]
  ],
  {e, hardcodedEdges}
]

maxMatchingSize = HopcroftKarp[g];
Print["Maximum matching size is ", maxMatchingSize]
Output:
Running tests...
Test 1: Result=1, Expected=1
Test 2: Result=2, Expected=2
Test 3: Result=3, Expected=3
Test 4: Result=0, Expected=0
All tests passed!

--- Running main execution with hard-coded input ---
Hard-coded graph dimensions: m=4, n=4, edges=6
Adding hard-coded edges:
  Adding edge: 2-based (2, 2), 1-based (1, 1) 
  Adding edge: 2-based (2, 4), 1-based (1, 3) 
  Adding edge: 2-based (3, 4), 1-based (2, 3) 
  Adding edge: 2-based (4, 5), 1-based (3, 4) 
  Adding edge: 2-based (5, 4), 1-based (4, 3) 
  Adding edge: 2-based (5, 3), 1-based (4, 2) 
Maximum matching size is 4



Works with: Pascal version Free Pascal 3.2.2
Translation of: C++
program HopcroftKarp;

{$mode objfpc}{$H+}

uses
  Classes, SysUtils, fgl;

const
  NIL_VERTEX = 0;
  INFINITE_LEVEL = MaxInt;

type
  // Edge structure
  TEdge = record
    from, to_: Integer;
  end;

  // Dynamic array of integers
  TIntArray = array of Integer;
  
  // Dynamic array of integer arrays
  TIntArray2D = array of TIntArray;

  // Bipartite graph class
  TBipartiteGraph = class
  private
    m, n: Integer;                           // Size of partitions
    adjacency_lists: TIntArray2D;            // Adjacency lists for U vertices
    pair_u, pair_v: TIntArray;               // Matching pairs
    levels: TIntArray;                       // BFS levels
    
    // Helper methods
    function BreadthFirstSearch: Boolean;
    function DepthFirstSearch(u: Integer): Boolean;
    
  public
    constructor Create(aM, aN: Integer);
    procedure AddEdge(u, v: Integer);
    function HopcroftKarpAlgorithm: Integer;
  end;

constructor TBipartiteGraph.Create(aM, aN: Integer);
begin
  m := aM;
  n := aN;
  
  // Initialize adjacency lists
  SetLength(adjacency_lists, m + 1);
  
  // Initialize matching arrays
  SetLength(pair_u, m + 1);
  SetLength(pair_v, n + 1);
  
  // Initialize levels array
  SetLength(levels, m + 1);
end;

procedure TBipartiteGraph.AddEdge(u, v: Integer);
begin
  if (u >= 1) and (u <= m) and (v >= 1) and (v <= n) then
  begin
    SetLength(adjacency_lists[u], Length(adjacency_lists[u]) + 1);
    adjacency_lists[u][High(adjacency_lists[u])] := v;
  end
  else
  begin
    raise Exception.Create('Attempt to add an edge (' + 
                          IntToStr(u) + ', ' + IntToStr(v) + 
                          ') which is out of bounds');
  end;
end;

function TBipartiteGraph.HopcroftKarpAlgorithm: Integer;
var
  u: Integer;
  matching_size: Integer;
begin
  // Initialize matching
  for u := 0 to m do
    pair_u[u] := NIL_VERTEX;
  for u := 0 to n do
    pair_v[u] := NIL_VERTEX;
    
  matching_size := 0;
  
  while BreadthFirstSearch do
  begin
    for u := 1 to m do
    begin
      if (pair_u[u] = NIL_VERTEX) and DepthFirstSearch(u) then
      begin
        Inc(matching_size);
      end;
    end;
  end;
  
  Result := matching_size;
end;

function TBipartiteGraph.BreadthFirstSearch: Boolean;
var
  queue: specialize TFPGList<Integer>;
  u, v, matched_u, i: Integer;
begin
  queue := specialize TFPGList<Integer>.Create;
  try
    // Initialize levels for vertices in U
    for u := 1 to m do
    begin
      if pair_u[u] = NIL_VERTEX then
      begin
        levels[u] := 0;
        queue.Add(u);
      end
      else
      begin
        levels[u] := INFINITE_LEVEL;
      end;
    end;
    
    // Level of NIL represents shortest augmenting path length
    levels[NIL_VERTEX] := INFINITE_LEVEL;
    
    while queue.Count > 0 do
    begin
      u := queue[0];
      queue.Delete(0);
      
      if levels[u] < levels[NIL_VERTEX] then
      begin
        // Explore neighbors v of u in V
        for i := 0 to High(adjacency_lists[u]) do
        begin
          v := adjacency_lists[u][i];
          matched_u := pair_v[v];
          
          if levels[matched_u] = INFINITE_LEVEL then
          begin
            levels[matched_u] := levels[u] + 1;
            queue.Add(matched_u);
          end;
        end;
      end;
    end;
    
    Result := (levels[NIL_VERTEX] <> INFINITE_LEVEL);
  finally
    queue.Free;
  end;
end;

function TBipartiteGraph.DepthFirstSearch(u: Integer): Boolean;
var
  v, matched_u, i: Integer;
begin
  if u <> NIL_VERTEX then
  begin
    // Explore neighbors v of u in V
    for i := 0 to High(adjacency_lists[u]) do
    begin
      v := adjacency_lists[u][i];
      matched_u := pair_v[v];
      
      // Check if edge leads to a vertex on shortest augmenting path
      if levels[matched_u] = levels[u] + 1 then
      begin
        if DepthFirstSearch(matched_u) then
        begin
          pair_v[v] := u;
          pair_u[u] := v;
          Result := True;
          Exit;
        end;
      end;
    end;
    
    // No augmenting path found, remove from DFS
    levels[u] := INFINITE_LEVEL;
    Result := False;
  end
  else
  begin
    // NIL vertex reached, augmenting path found
    Result := True;
  end;
end;

// Test function
function TestValue(testNumber, m, n: Integer; edges: array of TEdge; expected_result: Integer): Integer;
var
  graph: TBipartiteGraph;
  i: Integer;
  result_value: Integer;
begin
  graph := TBipartiteGraph.Create(m, n);
  try
    for i := 0 to High(edges) do
    begin
      graph.AddEdge(edges[i].from, edges[i].to_);
    end;
    
    result_value := graph.HopcroftKarpAlgorithm;
    WriteLn('Test ', testNumber, ': Result = ', result_value, ', Expected = ', expected_result);
    
    if result_value = expected_result then
    begin
      Result := 1;
    end
    else
    begin
      WriteLn('Test ', testNumber, ' failed.');
      Result := 0;
    end;
  finally
    graph.Free;
  end;
end;

// Main procedure
var
  success_count: Integer;
  edges: array of TEdge;
  i, j, idx: Integer;

begin
  WriteLn('Running tests:');
  success_count := 0;
  
  // Test Case 1
  SetLength(edges, 1);
  edges[0].from := 1;
  edges[0].to_ := 4;
  success_count := success_count + TestValue(1, 3, 5, edges, 1);
  
  // Test Case 2
  SetLength(edges, 3);
  edges[0].from := 1;
  edges[0].to_ := 4;
  edges[1].from := 1;
  edges[1].to_ := 5;
  edges[2].from := 5;
  edges[2].to_ := 1;
  success_count := success_count + TestValue(2, 6, 6, edges, 2);
  
  // Test Case 3: Complete Bipartite Graph K(3, 3)
  SetLength(edges, 9);
  idx := 0;
  for i := 1 to 3 do
  begin
    for j := 1 to 3 do
    begin
      edges[idx].from := i;
      edges[idx].to_ := j;
      Inc(idx);
    end;
  end;
  success_count := success_count + TestValue(3, 3, 3, edges, 3);
  
  // Test Case 4: No edges
  SetLength(edges, 0);
  success_count := success_count + TestValue(4, 2, 2, edges, 0);
  
  // Test Case 5
  SetLength(edges, 6);
  edges[0].from := 1;
  edges[0].to_ := 1;
  edges[1].from := 1;
  edges[1].to_ := 3;
  edges[2].from := 2;
  edges[2].to_ := 3;
  edges[3].from := 3;
  edges[3].to_ := 4;
  edges[4].from := 4;
  edges[4].to_ := 3;
  edges[5].from := 4;
  edges[5].to_ := 2;
  success_count := success_count + TestValue(5, 4, 4, edges, 4);
  
  if success_count = 5 then
  begin
    WriteLn('All tests passed.');
  end;
end.
Output:
Running tests:
Test 1: Result = 1, Expected = 1
Test 2: Result = 2, Expected = 2
Test 3: Result = 3, Expected = 3
Test 4: Result = 0, Expected = 0
Test 5: Result = 4, Expected = 4
All tests passed.


Works with: Perl version 5.22.1
Translation of: C++
#!/usr/bin/perl
use strict;
use warnings;
use List::Util qw(max);

# Representation of a bipartite graph
# Vertices in the left partition, U, are numbered from 1 to m,
# and vertices in the right partition, V, are numbered 1 to n.
package BipartiteGraph;

sub new {
    my ($class, $m, $n) = @_;
    my $self = {
        m => $m,
        n => $n,
        adjacency_lists => {},
        pair_u => {},
        pair_v => {},
        levels => {},
        NIL => 0,
        INFINITY => 999999999
    };
    
    # Initialize adjacency lists
    for my $u (1..$m) {
        $self->{adjacency_lists}{$u} = [];
    }
    
    # Initialize pairs
    for my $u (1..$m) {
        $self->{pair_u}{$u} = $self->{NIL};
    }
    for my $v (1..$n) {
        $self->{pair_v}{$v} = $self->{NIL};
    }
    
    # Initialize levels
    for my $u (1..$m) {
        $self->{levels}{$u} = $self->{INFINITY};
    }
    
    bless $self, $class;
    return $self;
}

sub add_edge {
    my ($self, $u, $v) = @_;
    
    if ($u >= 1 && $u <= $self->{m} && $v >= 1 && $v <= $self->{n}) {
        push @{$self->{adjacency_lists}{$u}}, $v;
    } else {
        die "Attempt to add an edge ($u, $v) which is out of bounds\n";
    }
}

# Return the matching size of the bipartite graph
sub hopcroft_karp_algorithm {
    my ($self) = @_;
    
    # Reset pairs
    for my $u (1..$self->{m}) {
        $self->{pair_u}{$u} = $self->{NIL};
    }
    for my $v (1..$self->{n}) {
        $self->{pair_v}{$v} = $self->{NIL};
    }
    
    my $matching_size = 0;
    
    while ($self->breadth_first_search()) {
        for my $u (1..$self->{m}) {
            if ($self->{pair_u}{$u} == $self->{NIL} && $self->depth_first_search($u)) {
                # vertex u is free and an augmenting path starting
                # from u has been found by the depth first search
                $matching_size++;
            }
        }
    }
    
    return $matching_size;
}

# Determines whether there exists an augmenting path starting from a free vertex in U.
# Return true if an augmenting path could exist, otherwise false.
sub breadth_first_search {
    my ($self) = @_;
    
    my @queue = ();
    
    # Initialize 'levels' for the vertices in U
    for my $u (1..$self->{m}) {
        if ($self->{pair_u}{$u} == $self->{NIL}) {
            # If u is a free vertex, its level is 0 and it is added to the queue
            $self->{levels}{$u} = 0;
            push @queue, $u;
        } else {
            # Otherwise, set 'levels' to infinity
            $self->{levels}{$u} = $self->{INFINITY};
        }
    }
    
    # The 'level' to the NIL node represents the length of the shortest augmenting path
    $self->{levels}{$self->{NIL}} = $self->{INFINITY};
    
    while (@queue) {
        my $u = shift @queue;
        
        if ($self->{levels}{$u} < $self->{levels}{$self->{NIL}}) {
            # The path through u could lead to a shorter augmenting path
            for my $v (@{$self->{adjacency_lists}{$u}}) {
                # Explore the neighbours v of u in V
                my $matched_u = $self->{pair_v}{$v};
                if ($self->{levels}{$matched_u} == $self->{INFINITY}) {
                    # The matched vertex has not been visited yet
                    $self->{levels}{$matched_u} = $self->{levels}{$u} + 1;
                    push @queue, $matched_u; # Enqueue the matched vertex to explore it further
                }
            }
        }
    }
    
    # An augmenting path from the initial free vertices was found if levels[NIL] is not INFINITY
    return $self->{levels}{$self->{NIL}} != $self->{INFINITY};
}

# Determine whether the shortest path from vertex u in U found by breadth_first_search() can be augmented.
# Return true if an augmenting path was found starting from u, otherwise false.
sub depth_first_search {
    my ($self, $u) = @_;
    
    if ($u != $self->{NIL}) {
        for my $v (@{$self->{adjacency_lists}{$u}}) {
            # Explore neighbours v of u in V
            my $matched_u = $self->{pair_v}{$v};
            # Check whether the edge (u, v) leads to a vertex matched_u
            # such that the path u -> v -> matched_u is part of a shortest augmenting path
            if ($self->{levels}{$matched_u} == $self->{levels}{$u} + 1) {
                if ($self->depth_first_search($matched_u)) {
                    # An augmenting path is found starting from 'matched_u'
                    $self->{pair_v}{$v} = $u; # Match v with u,
                    $self->{pair_u}{$u} = $v; # and u with v
                    return 1;
                }
            }
        }
        
        # No augmenting path was found starting from vertex u through any of its neighbours v,
        # so remove u from the depth first search phase of the algorithm
        $self->{levels}{$u} = $self->{INFINITY};
        return 0;
    }
    
    return 1;
}

package main;

sub test_value {
    my ($test_number, $m, $n, $edges_ref, $expected_result) = @_;
    
    my $graph = BipartiteGraph->new($m, $n);
    
    for my $edge (@$edges_ref) {
        $graph->add_edge($edge->{from}, $edge->{to});
    }
    
    my $result = $graph->hopcroft_karp_algorithm();
    print "Test $test_number: Result = $result, Expected = $expected_result\n";
    
    if ($result == $expected_result) {
        return 1;
    }
    
    print "Test $test_number failed.\n";
    return 0;
}

# Main execution
print "Running tests:\n";
my $success_count = 0;

# Test Case 1
$success_count += test_value(1, 3, 5, [{from => 1, to => 4}], 1);

# Test Case 2
$success_count += test_value(2, 6, 6, [
    {from => 1, to => 4},
    {from => 1, to => 5},
    {from => 5, to => 1}
], 2);

# Test Case 3: Complete Bipartite Graph K(3, 3)
my @edges = ();
for my $i (1..3) {
    for my $j (1..3) {
        push @edges, {from => $i, to => $j};
    }
}
$success_count += test_value(3, 3, 3, \@edges, 3);

# Test Case 4: No edges
$success_count += test_value(4, 2, 2, [], 0);

# Test Case 5
@edges = (
    {from => 1, to => 1},
    {from => 1, to => 3},
    {from => 2, to => 3},
    {from => 3, to => 4},
    {from => 4, to => 3},
    {from => 4, to => 2}
);
$success_count += test_value(5, 4, 4, \@edges, 4);

if ($success_count == 5) {
    print "All tests passed.\n";
}
Output:
Running tests:
Test 1: Result = 1, Expected = 1
Test 2: Result = 2, Expected = 2
Test 3: Result = 3, Expected = 3
Test 4: Result = 0, Expected = 0
Test 5: Result = 4, Expected = 4
All tests passed.





Translation of: Go
integer m, n
sequence adj, pairU, pairV, dist

constant inf = 1e308*1e308,
         NIL = 1

function BFS()
    sequence queue := {}
        
    for u=2 to m+1 do
        if pairU[u] == NIL then
            dist[u] = 0
            queue &= u
        else
            dist[u] = inf
        end if
    end for
        
    dist[NIL] = inf
        
    while length(queue) do
        integer u := queue[1]
        queue = queue[2..$]
        if dist[u] < dist[NIL] then
            for v in adj[u] do
                integer matchedU := pairV[v]
                if is_inf(dist[matchedU]) then
                    dist[matchedU] = dist[u] + 1
                    queue &= matchedU
                end if
            end for
        end if
    end while
        
    return not is_inf(dist[NIL])
end function

function DFS(integer u)
    if u != NIL then
        for v in adj[u] do
            integer matchedU := pairV[v]
            if dist[matchedU] == dist[u]+1 then
                if DFS(matchedU) then
                    pairV[v] = u
                    pairU[u] = v
                    return true
                end if
            end if
        end for
        dist[u] = inf
        return false
    end if
    return true
end function

function HopcroftKarpAlgorithm()
    pairU := repeat(NIL, m+1)
    pairV := repeat(NIL, n+1)
    integer matchingSize := 0
        
    while BFS() do
        for u=2 to m+1 do
            if pairU[u] == NIL and DFS(u) then
                matchingSize += 1
            end if
        end for
    end while
        
    return matchingSize
end function

procedure test(integer id, _m, _n, expected, sequence edges, bool verbose=false)
    m = _m
    n = _n
    adj := repeat({},m+1)
    dist := repeat(0, m+1)
    if verbose then
        integer e := length(edges)
        printf(1,"Hard-coded graph dimensions: m=%d, n=%d, edges=%d\n", {m, n, e})
        printf(1,"Adding hard-coded edges:\n")
    end if
    for edge in edges do
        integer {u,v} = edge
        if verbose then
            printf(1,"  Adding edge: (%d, %d)\n", {u, v})
        end if
        assert(1<=u and u<=m and 1<=v and v<=n)
        adj[u+1] = append(adj[u+1], v+1)
    end for
    integer actual := HopcroftKarpAlgorithm()
    if verbose then
        printf(1,"\nMaximum matching size is %d\n", actual)
    else
        printf(1,"Test %d: Result=%d, Expected=%d\n", {id,actual,expected})
        assert(actual==expected)
    end if
end procedure

printf(1,"Running tests...\n")

test(1,3,5,1,{{1,4}})
test(2,6,6,2,{{1,4},{1,5},{5,1}})
test(3,3,3,3,{{1,1},{1,2},{1,3},
              {2,1},{2,2},{2,3},
              {3,1},{3,2},{3,3}})
test(4,2,2,0,{})
printf(1,"All tests passed!\n")
        
printf(1,"\n--- Running main execution with hard-coded input ---\n")
        
constant hardcodedEdges := {{1,1},{1,3},{2,3},{3,4},{4,3},{4,2}}
test(0,4,4,0,hardcodedEdges,true)
Output:
Running tests...
Test 1: Result=1, Expected=1
Test 2: Result=2, Expected=2
Test 3: Result=3, Expected=3
Test 4: Result=0, Expected=0
All tests passed!

--- Running main execution with hard-coded input ---
Hard-coded graph dimensions: m=4, n=4, edges=6
Adding hard-coded edges:
  Adding edge: (1, 1)
  Adding edge: (1, 3)
  Adding edge: (2, 3)
  Adding edge: (3, 4)
  Adding edge: (4, 3)
  Adding edge: (4, 2)

Maximum matching size is 4
import collections

# Setting a large value for infinity, equivalent to C++ INT_MAX for distance calculations
INF = float('inf')
# Using 0 as the NIL node, consistent with the C++ code
NIL = 0


class HKGraph:
    """
    Implementation of the Hopcroft-Karp algorithm for finding maximum matching
    in a bipartite graph.

    Assumes vertices in the left partition (U) are numbered 1 to m,
    and vertices in the right partition (V) are numbered 1 to n.
    The NIL node is represented by 0.
    """

    def __init__(self, m, n):
        """
        Constructor for the HKGraph class.

        Args:
            m (int): Number of vertices in the left partition (U).
            n (int): Number of vertices in the right partition (V).
        """
        self.m = m  # Number of vertices on the left side (U)
        self.n = n  # Number of vertices on the right side (V)

        # Adjacency list: adj[u] contains list of neighbors of u in V
        # Initialize with empty lists for vertices 1 to m
        self.adj = [[] for _ in range(m + 1)]

        # Matching pairs:
        # pair_u[u] stores the vertex v in V matched with u in U (or NIL if unmatched)
        self.pair_u = [NIL] * (m + 1)
        # pair_v[v] stores the vertex u in U matched with v in V (or NIL if unmatched)
        self.pair_v = [NIL] * (n + 1)

        # dist[u] stores the distance (level) of vertex u in U during BFS
        # Initialized within the hopcroft_karp_algorithm or bfs method
        self.dist = [INF] * (m + 1)

    def add_edge(self, u, v):
        """
        Adds a directed edge from vertex u (left partition) to vertex v (right partition).

        Args:
            u (int): Vertex index in the left partition (1 to m).
            v (int): Vertex index in the right partition (1 to n).
        """
        # Ensure vertices are within the valid range
        if 1 <= u <= self.m and 1 <= v <= self.n:
            self.adj[u].append(v)  # Add v to u's adjacency list
        else:
            # Optionally print a warning for edges added outside the defined range
            # This check is now also done in the main section when adding edges.
            # print(f"Warning: Attempted to add edge ({u}, {v}) outside graph bounds [1..{self.m}], [1..{self.n}]")
            pass

    def bfs(self) -> bool:
        """
        Performs Breadth-First Search (BFS) to find layers in the graph.
        It checks if there exists an augmenting path starting from a free vertex in U.

        Returns:
            bool: True if an augmenting path might exist (dist[NIL] is finite),
                  False otherwise.
        """
        queue = collections.deque()  # Use deque for efficient queue operations

        # Initialize distances for vertices in U
        for u in range(1, self.m + 1):
            if self.pair_u[u] == NIL:
                # If u is a free vertex, its distance is 0, add to queue
                self.dist[u] = 0
                queue.append(u)
            else:
                # Otherwise, set distance to infinity initially
                self.dist[u] = INF

        # Distance to the NIL node represents the length of the shortest augmenting path
        self.dist[NIL] = INF

        while queue:
            u = queue.popleft()  # Dequeue a vertex from U

            # If the path through u can potentially lead to a shorter augmenting path
            if self.dist[u] < self.dist[NIL]:
                # Explore neighbors v of u in V
                for v in self.adj[u]:
                    matched_u = self.pair_v[v]  # Get the vertex u' matched with v
                    # If the matched vertex u' hasn't been visited yet (its distance is INF)
                    if self.dist[matched_u] == INF:
                        # Set the distance of u' based on u
                        self.dist[matched_u] = self.dist[u] + 1
                        # Enqueue u' to explore further
                        queue.append(matched_u)

        # If dist[NIL] is still INF, no augmenting path was found originating
        # from the initial free vertices. Otherwise, augmenting paths might exist.
        return self.dist[NIL] != INF

    def dfs(self, u: int) -> bool:
        """
        Performs Depth-First Search (DFS) starting from vertex u in U
        to find and augment along a shortest path identified by BFS.

        Args:
            u (int): The current vertex in U being visited (or NIL).

        Returns:
            bool: True if an augmenting path was found and used starting from u,
                  False otherwise.
        """
        if u != NIL:
            # Explore neighbors v of u in V
            for v in self.adj[u]:
                matched_u = self.pair_v[v]  # Get the vertex u' matched with v
                # Check if the edge (u, v) leads to a vertex u'
                # such that the path u -> v -> u' is part of a shortest augmenting path
                if self.dist[matched_u] == self.dist[u] + 1:
                    # Recursively call DFS on u'
                    if self.dfs(matched_u):
                        # If an augmenting path is found starting from u',
                        # update the matching: match v with u, and u with v
                        self.pair_v[v] = u
                        self.pair_u[u] = v
                        return True  # Augmentation successful

            # If no augmenting path was found starting from u through any neighbor v,
            # mark u as visited in this DFS phase by setting its distance to INF
            self.dist[u] = INF
            return False  # Augmentation failed for this path

        # Base case: If u is NIL, it means we have reached the end of an alternating path
        # originating from a free vertex in U and ending at a free vertex in V (represented by NIL).
        return True

    def hopcroft_karp_algorithm(self) -> int:
        """
        Executes the Hopcroft-Karp algorithm to find the maximum matching.

        Returns:
            int: The size of the maximum matching found.
        """
        # Initialize matching pairs to NIL (unmatched)
        self.pair_u = [NIL] * (self.m + 1)
        self.pair_v = [NIL] * (self.n + 1)

        matching_size = 0  # Initialize the size of the matching

        # Keep finding augmenting paths using BFS and DFS until no more exist
        while self.bfs():
            # For every free vertex u in U
            for u in range(1, self.m + 1):
                # If u is free and an augmenting path starting from u is found via DFS
                if self.pair_u[u] == NIL and self.dfs(u):
                    # Increment the matching size
                    matching_size += 1

        return matching_size


# --- Testing ---

def tests():
    """ Runs test cases for the Hopcroft-Karp implementation. """
    print("Running tests...")

    # Test Case 1 (Corrected from C++ version - using 1-based indexing)
    # m=3, n=5, edges = [(1, 4)] - Expected matching size = 1
    g1 = HKGraph(3, 5)
    g1.add_edge(1, 4)
    res1 = g1.hopcroft_karp_algorithm()
    expected_res1 = 1
    print(f"Test 1: Result={res1}, Expected={expected_res1}")
    assert res1 == expected_res1, f"Test 1 Failed: Expected {expected_res1}, got {res1}"

    # Test Case 2 (Corrected from C++ version - using 1-based indexing, assuming (5,0) meant (5,1) or similar valid edge)
    # m=6, n=6, edges = [(1,4), (1,5), (5,1)]
    # Expected matching size = 2 (e.g., (1,4), (5,1) or (1,5), (5,1))
    # Note: Original C++ test had (0,1) and (5,0) which are problematic with 1-based index logic.
    g2 = HKGraph(6, 6)
    # g3.add_edge(0,1) # Invalid vertex 0 in U
    g2.add_edge(1, 4)
    g2.add_edge(1, 5)
    g2.add_edge(5, 1)  # Assuming (5,0) meant a valid edge like (5,1)
    # g2.add_edge(5, 0) # Invalid vertex 0 in V
    res2 = g2.hopcroft_karp_algorithm()
    expected_res2 = 2
    print(f"Test 2: Result={res2}, Expected={expected_res2}")
    assert res2 == expected_res2, f"Test 3 Failed: Expected {expected_res2}, got {res2}"

    # Test Case 3: Complete Bipartite Graph K_{3,3}
    # m=3, n=3, all possible edges. Expected matching size = 3
    g3 = HKGraph(3, 3)
    for i in range(1, 4):
        for j in range(1, 4):
            g3.add_edge(i, j)
    res3 = g3.hopcroft_karp_algorithm()
    expected_res3 = 3
    print(f"Test 3: Result={res3}, Expected={expected_res3}")
    assert res3 == expected_res3, f"Test 4 Failed: Expected {expected_res3}, got {res3}"

    # Test Case 4: No edges
    # m=2, n=2, no edges. Expected matching size = 0
    g4 = HKGraph(2, 2)
    res4 = g4.hopcroft_karp_algorithm()
    expected_res4 = 0
    print(f"Test 4: Result={res4}, Expected={expected_res4}")
    assert res4 == expected_res4, f"Test 4 Failed: Expected {expected_res4}, got {res4}"

    print("All tests passed!")


# --- Main execution ---

if __name__ == "__main__":
    # Run self-tests first
    tests()
    print("\n--- Running main execution with hard-coded input ---")

    # --- Hard-coded input data ---
    # Example 1: Corresponds to Test Case 2
    hardcoded_v1 = 4  # Number of vertices in left partition (m)
    hardcoded_v2 = 4  # Number of vertices in right partition (n)
    hardcoded_edges = [
        (1, 1),
        (1, 3),
        (2, 3),
        (3, 4),
        (4, 3),
        (4, 2)
    ]
    # Expected output for Example 1: 3

    # Use the selected hardcoded data
    v1 = hardcoded_v1
    v2 = hardcoded_v2
    edges_data = hardcoded_edges
    e = len(edges_data)  # Number of edges is derived from the list

    # Create the graph object
    g = HKGraph(v1, v2)

    print(f"Hard-coded graph dimensions: m={v1}, n={v2}, edges={e}")
    print("Adding hard-coded edges:")

    # Add edges from the hard-coded list
    for u, v in edges_data:
        print(f"  Adding edge: ({u}, {v})")
        # Add edge only if vertices are within valid 1-based range
        if 1 <= u <= v1 and 1 <= v <= v2:
            g.add_edge(u, v)
        else:
            # This warning is important if hardcoded data might be invalid
            print(f"Warning: Skipping invalid hard-coded edge ({u}, {v}) - indices out of range [1..{v1}] or [1..{v2}]")

    # Run the Hopcroft-Karp algorithm
    max_matching_size = g.hopcroft_karp_algorithm()

    # Print the result
    print(f"\nMaximum matching size is {max_matching_size}")
Output:
Running tests...
Test 1: Result=1, Expected=1
Test 2: Result=2, Expected=2
Test 3: Result=3, Expected=3
Test 4: Result=0, Expected=0
All tests passed!

--- Running main execution with hard-coded input ---
Hard-coded graph dimensions: m=4, n=4, edges=6
Adding hard-coded edges:
  Adding edge: (1, 1)
  Adding edge: (1, 3)
  Adding edge: (2, 3)
  Adding edge: (3, 4)
  Adding edge: (4, 3)
  Adding edge: (4, 2)

Maximum matching size is 4


R

You may try the code at https://rdrr.io/snippets/

Works with: R
Translation of: Julia
library(R6)

# Constants
NIL <- 1
INF <- .Machine$integer.max

# HKGraph class
HKGraph <- R6Class("HKGraph",
  public = list(
    m = NULL,
    n = NULL,
    adj = NULL,
    pair_u = NULL,
    pair_v = NULL,
    dist = NULL,

    initialize = function(m, n) {
      self$m <- m
      self$n <- n
      self$adj <- vector("list", m + 1)
      self$pair_u <- rep(NIL, m + 1)
      self$pair_v <- rep(NIL, n + 1)
      self$dist <- rep(INF, m + 1)
    },

    add_edge = function(u, v) {
      if (u >= 2 & u <= self$m + 1 & v >= 2 & v <= self$n + 1) {
        self$adj[[u]] <- c(self$adj[[u]], v)
      } else {
        warning(paste0("Attempted to add edge (", u, ", ", v, ") outside graph bounds [2..", self$m + 1, "], [2..", self$n + 1, "]"))
      }
    }
  )
)

# BFS function
bfs <- function(g) {
  queue <- c()
  # Reset distances
  for (u in 2:(g$m + 1)) {
    if (g$pair_u[u] == NIL) {
      g$dist[u] <- 0
      queue <- c(queue, u)
    } else {
      g$dist[u] <- INF
    }
  }
  g$dist[NIL] <- INF

  while (length(queue) > 0) {
    u <- queue[1]
    queue <- queue[-1]
    if (g$dist[u] < g$dist[NIL]) {
      for (v in g$adj[[u]]) {
        matched_u <- g$pair_v[v]
        if (g$dist[matched_u] == INF) {
          g$dist[matched_u] <- g$dist[u] + 1
          queue <- c(queue, matched_u)
        }
      }
    }
  }
  return(g$dist[NIL] != INF)
}

# DFS function
dfs <- function(g, u) {
  if (u != NIL) {
    for (v in g$adj[[u]]) {
      matched_u <- g$pair_v[v]
      if (g$dist[matched_u] == g$dist[u] + 1) {
        if (dfs(g, matched_u)) {
          g$pair_v[v] <- u
          g$pair_u[u] <- v
          return(TRUE)
        }
      }
    }
    g$dist[u] <- INF
    return(FALSE)
  }
  return(TRUE)
}

# Main Hopcroft-Karp function
hopcroft_karp_algorithm <- function(g) {
  # Reset matching
  g$pair_u <- rep(NIL, g$m + 1)
  g$pair_v <- rep(NIL, g$n + 1)
  matching_size <- 0

  while (bfs(g)) {
    for (u in 2:(g$m + 1)) {
      if (g$pair_u[u] == NIL & dfs(g, u)) {
        matching_size <- matching_size + 1
      }
    }
  }

  return(matching_size)
}

# Test cases
hk_g_tests <- function() {
  cat("Running tests...\n")

  # Test 1
  g1 <- HKGraph$new(3, 5)
  g1$add_edge(2, 5)
  res1 <- hopcroft_karp_algorithm(g1)
  expected_res1 <- 1
  cat("Test 1: Result =", res1, ", Expected =", expected_res1, "\n")
  stopifnot(res1 == expected_res1)

  # Test 2
  g2 <- HKGraph$new(6, 6)
  g2$add_edge(2, 5)
  g2$add_edge(2, 6)
  g2$add_edge(6, 2)
  res2 <- hopcroft_karp_algorithm(g2)
  expected_res2 <- 2
  cat("Test 2: Result =", res2, ", Expected =", expected_res2, "\n")
  stopifnot(res2 == expected_res2)

  # Test 3: Complete Bipartite Graph K_{3,3}
  g3 <- HKGraph$new(3, 3)
  for (i in 2:4) {
    for (j in 2:4) {
      g3$add_edge(i, j)
    }
  }
  res3 <- hopcroft_karp_algorithm(g3)
  expected_res3 <- 3
  cat("Test 3: Result =", res3, ", Expected =", expected_res3, "\n")
  stopifnot(res3 == expected_res3)

  # Test 4: No edges
  g4 <- HKGraph$new(2, 2)
  res4 <- hopcroft_karp_algorithm(g4)
  expected_res4 <- 0
  cat("Test 4: Result =", res4, ", Expected =", expected_res4, "\n")
  stopifnot(res4 == expected_res4)

  cat("All tests passed!\n")
}

# Hard-coded example run
all_hk_tests <- function() {
  hk_g_tests()
  cat("\n--- Running main execution with hard-coded input ---\n")

  # Hardcoded data
  hardcoded_v1 <- 4
  hardcoded_v2 <- 4
  hardcoded_edges <- list(
    c(2, 2),
    c(2, 4),
    c(3, 4),
    c(4, 5),
    c(5, 4),
    c(5, 3)
  )

  v1 <- hardcoded_v1
  v2 <- hardcoded_v2
  edges_data <- hardcoded_edges
  e_len <- length(edges_data)

  g <- HKGraph$new(v1, v2)
  cat("Hard-coded graph dimensions: m=", v1, ", n=", v2, ", edges=", e_len, "\n")
  cat("Adding hard-coded edges:\n")

  for (edge in edges_data) {
    u <- edge[1]
    v <- edge[2]
    cat("  Adding edge: 2-based (", u, ", ", v, ")\n")
    if (u >= 2 & u <= v1 + 1 & v >= 2 & v <= v2 + 1) {
      g$add_edge(u, v)
    } else {
      cat("Warning: Skipping invalid hard-coded edge (", u, ", ", v, ")\n")
    }
  }

  max_matching_size <- hopcroft_karp_algorithm(g)
  cat("Maximum matching size is", max_matching_size, "\n")
}

# Run all tests and main example
all_hk_tests()
Output:
Running tests...
Test 1: Result = 1 , Expected = 1 
Test 2: Result = 2 , Expected = 2 
Test 3: Result = 3 , Expected = 3 
Test 4: Result = 0 , Expected = 0 
All tests passed!

--- Running main execution with hard-coded input ---
Hard-coded graph dimensions: m= 4 , n= 4 , edges= 6 
Adding hard-coded edges:
  Adding edge: 2-based ( 2 ,  2 )
  Adding edge: 2-based ( 2 ,  4 )
  Adding edge: 2-based ( 3 ,  4 )
  Adding edge: 2-based ( 4 ,  5 )
  Adding edge: 2-based ( 5 ,  4 )
  Adding edge: 2-based ( 5 ,  3 )
Maximum matching size is 4 



Translation of: Go
Translation of: Java
# 20250708 Raku programming solution

# Edge record to represent graph edges
class Edge { has Int ($.from, $.to);
   method new(Int $from, Int $to) { self.bless(:$from, :$to) }
}

# BipartiteGraph class implementing the Hopcroft-Karp algorithm
class BipartiteGraph {
   has Int $.m;  # Number of vertices in left partition U
   has Int $.n;  # Number of vertices in right partition V
   has @.adj;    # Adjacency lists for vertices in U
   has @.pair-u; # Matching for vertices in U
   has @.pair-v; # Matching for vertices in V
   has @.levels; # Distance levels for BFS
    
   # Constants
   constant NIL = 0;
   constant INFINITY = Inf; 
    
   method new(Int $m, Int $n where * > 0) {
      my @adj    =    [] xx ($m + 1);         
      my @pair-u = (NIL) xx ($m + 1);
      my @pair-v = (NIL) xx ($n + 1);
      my @levels =   Inf xx ($m + 1);
      return self.bless(:$m, :$n, :@adj, :@pair-u, :@pair-v, :@levels);
   }
    
   method add-edge( Int $u where 1 <= * <= $.m, Int $v where  1 <= * <= $.n ) {
      return @!adj[$u].push: $v;
   }
    
   method hopcroft-karp-algorithm() returns Int {
      @!pair-u = (NIL) xx ($!m + 1); # Reset
      @!pair-v = (NIL) xx ($!n + 1); # Reset
      my Int $matching-size = 0;
        
      while self.breadth-first-search() {
         for 1..$!m -> $u {
            # Check if vertex u is free and can be part of an augmenting path
            $matching-size++ if @!pair-u[$u] == NIL and $.depth-first-search($u)
         }
      } # Continue until no more augmenting paths can be found
      return $matching-size
   }
    
   # BFS to find if augmenting paths exist and compute shortest path lengths
   method breadth-first-search() returns Bool {
      my @queue = gather for 1..$!m -> $u { # Initialize levels for vertices  
         @!pair-u[$u] == NIL ?? ( @!levels[take $u] = 0   ) 
                             !! ( @!levels[$u] = INFINITY )
      }
        
      # NIL vertex level represents shortest augmenting path length
      @!levels[NIL] = INFINITY;
        
      # BFS traversal
      while my $u = @queue.shift {     
         # Only process if this path could lead to a shorter augmenting path
         if @!levels[$u] < @!levels[NIL] {
            for @!adj[$u].list -> $v {
               my $matched-u = @!pair-v[$v];
                    
               # If the matched vertex hasn't been visited yet
               if @!levels[$matched-u] == INFINITY {
                  @!levels[$matched-u] = @!levels[$u] + 1;
                  @queue.push($matched-u);
               }
            }
         }
      }
      return @!levels[NIL] != INFINITY; # true if an augmenting path was found
   }
    
   # DFS to find and construct augmenting paths
   method depth-first-search(Int $u) returns Bool {
      if $u != NIL {
         for @!adj[$u].list -> $v {
            my $matched-u = @!pair-v[$v];
                
            # Check if this edge is part of a shortest augmenting path
            if @!levels[$matched-u] == @!levels[$u] + 1 {
               # Recursively try to find augmenting path from matched vertex
               if self.depth-first-search($matched-u) {
                  (@!pair-v[$v], @!pair-u[$u]) = $u, $v; # Update matching
                  return True;
               }
            }
         }
         @!levels[$u] = INFINITY; # No augmenting path found through this vertex
         return False;
      }
      return True;
   }
}

# Test runner function
sub test-case(Int $test-num, Int $m, Int $n, @edges, Int $expected) returns Bool {
   my $graph = BipartiteGraph.new($m, $n);
    
   for @edges -> $edge { $graph.add-edge($edge.from, $edge.to) }
    
   my $result = $graph.hopcroft-karp-algorithm();
   say "Test $test-num: Result = $result, Expected = $expected";
    
   return True if $result == $expected;
   say "Test $test-num failed!";
   return False;
}

say "Running Hopcroft-Karp Algorithm Tests:";
my $success-count = 0;
    
# Test Case 1: Simple case
$success-count += test-case(1, 3, 5, [Edge.new(1, 4)], 1) ?? 1 !! 0;
    
# Test Case 2: Multiple edges
$success-count += test-case(
   2, 6, 6, [ Edge.new(1, 4), Edge.new(1, 5), Edge.new(5, 1) ], 2
) ?? 1 !! 0;
    
# Test Case 3: Complete bipartite graph K(3,3)
my @edges = gather for 1..3 X 1..3 -> ($i,$j) { take Edge.new($i,$j) };   
$success-count += test-case(3, 3, 3, @edges, 3) ?? 1 !! 0;
    
# Test Case 4: No edges
$success-count += test-case(4, 2, 2, [], 0) ?? 1 !! 0;
    
# Test Case 5: Complex case
$success-count += test-case(
   5, 4, 4, [ Edge.new(1, 1), Edge.new(1, 3), Edge.new(2, 3),
              Edge.new(3, 4), Edge.new(4, 3), Edge.new(4, 2) ], 4
) ?? 1 !! 0;
    
say  $success-count == 5 ?? "All tests passed!\n"
                         !! "$success-count/5 tests passed.\n";
    
say "Running main execution with hard-coded input:";
    
my ($hardcoded-m, $hardcoded-n) = 4, 4;
my @hardcoded-edges = [ [1, 1], [1, 3], [2, 3], [3, 4], [4, 3], [4, 2] ];
    
my $graph = BipartiteGraph.new($hardcoded-m, $hardcoded-n);
say "Hard-coded graph dimensions: m=$hardcoded-m, n=$hardcoded-n, edges={@hardcoded-edges.elems}";
say "Adding hard-coded edges:";
    
for @hardcoded-edges -> [$u, $v] {
   say "  Adding edge: ($u, $v)";
        
   if 1 <= $u <= $hardcoded-m and 1 <= $v <= $hardcoded-n {
      $graph.add-edge($u, $v);
   } else {
      say "Warning: Skipping invalid edge ($u, $v) - indices out of range";
   }
}
say "\nMaximum matching size is $graph.hopcroft-karp-algorithm()";

You may Attempt This Online!

Translation of: C++
use std::collections::VecDeque;
use std::u32;

/// Representation of a bipartite graph.
/// Vertices in the left partition, U, are numbered from 1 to m,
/// and vertices in the right partition, V, are numbered 1 to n.
struct BipartiteGraph {
    adjacency_lists: Vec<Vec<u32>>, // adjacency_lists(u) stores a list of neighbours of u in V
    pair_u: Vec<u32>, // pair_u(u) stores the vertex v in V matched with u in U, or NIL if unmatched
    pair_v: Vec<u32>, // pair_v(v) stores the vertex u in U matched with v in V, or NIL if unmatched
    levels: Vec<u32>, // levels(u) stores the level of vertex u in U during a breadth first search
    m: u32, // Index of the vertices in the left partition
    n: u32, // Index of the vertices in the right partition
    nil: u32,
    infinity: u32,
}

impl BipartiteGraph {
    fn new(m: u32, n: u32) -> Self {
        let nil = 0;
        let infinity = u32::MAX;
        
        let mut adjacency_lists = Vec::with_capacity((m + 1) as usize);
        for _ in 0..=m {
            adjacency_lists.push(Vec::new());
        }
        
        let pair_u = vec![nil; (m + 1) as usize];
        let pair_v = vec![nil; (n + 1) as usize];
        let levels = vec![infinity; (m + 1) as usize];
        
        BipartiteGraph {
            adjacency_lists,
            pair_u,
            pair_v,
            levels,
            m,
            n,
            nil,
            infinity,
        }
    }

    fn add_edge(&mut self, u: u32, v: u32) -> Result<(), String> {
        if 1 <= u && u <= self.m && 1 <= v && v <= self.n {
            self.adjacency_lists[u as usize].push(v);
            Ok(())
        } else {
            Err(format!("Attempt to add an edge ({}, {}) which is out of bounds", u, v))
        }
    }

    /// Return the matching size of the bipartite graph.
    fn hopcroft_karp_algorithm(&mut self) -> u32 {
        self.pair_u = vec![self.nil; (self.m + 1) as usize];
        self.pair_v = vec![self.nil; (self.n + 1) as usize];
        let mut matching_size = 0;

        while self.breadth_first_search() {
            for u in 1..=self.m {
                // vertex u is free and an augmenting path starting
                // from u has been found by the depth first search
                if self.pair_u[u as usize] == self.nil && self.depth_first_search(u) {
                    matching_size += 1;
                }
            }
        }
        matching_size
    }

    /// Determines whether there exists an augmenting path starting from a free vertex in U.
    ///
    /// Return true if an augmenting path could exist, otherwise false.
    fn breadth_first_search(&mut self) -> bool {
        let mut queue = VecDeque::new();
        
        // Initialize 'levels' for the vertices in U
        for u in 1..=self.m {
            if self.pair_u[u as usize] == self.nil {
                // If u is a free vertex, its level is 0 and it is added to the queue
                self.levels[u as usize] = 0;
                queue.push_back(u);
            } else {
                // Otherwise, set 'levels' to infinity
                self.levels[u as usize] = self.infinity;
            }
        }

        // The 'level' to the NIL node represents the length of the shortest augmenting path
        self.levels[self.nil as usize] = self.infinity;

        while !queue.is_empty() {
            let u = queue.pop_front().unwrap();
            
            // The path through u could lead to a shorter augmenting path
            if self.levels[u as usize] < self.levels[self.nil as usize] {
                // Explore the neighbours v of u in V
                for &v in &self.adjacency_lists[u as usize] {
                    let matched_u = self.pair_v[v as usize];
                    
                    // The matched vertex has not been visited yet
                    if self.levels[matched_u as usize] == self.infinity {
                        self.levels[matched_u as usize] = self.levels[u as usize] + 1;
                        // Enqueue the matched vertex to explore it further
                        queue.push_back(matched_u);
                    }
                }
            }
        }

        // An augmenting path from the initial free vertices was found if levels[NIL] is not INFINITY
        self.levels[self.nil as usize] != self.infinity
    }

    /// Determine whether the shortest path from vertex u in U found by breadth_first_search() can be augmented.
    ///
    /// Return true if an augmenting path was found starting from u, otherwise false.
    fn depth_first_search(&mut self, u: u32) -> bool {
        if u != self.nil {
            // Explore neighbours v of u in V
            for &v in &self.adjacency_lists[u as usize].clone() {
                let matched_u = self.pair_v[v as usize];
                
                // Check whether the edge (u, v) leads to a vertex matched_u
                // such that the path u -> v -> matched_u is part of a shortest augmenting path
                if self.levels[matched_u as usize] == self.levels[u as usize] + 1 {
                    // An augmenting path is found starting from 'matched_u'
                    if self.depth_first_search(matched_u) {
                        // Match v with u and u with v
                        self.pair_v[v as usize] = u;
                        self.pair_u[u as usize] = v;
                        return true;
                    }
                }
            }

            // No augmenting path was found starting from vertex u through any of its neighbours v,
            // so remove u from the depth first search phase of the algorithm
            self.levels[u as usize] = self.infinity;
            false
        } else {
            true
        }
    }
}

struct Edge {
    from: u32,
    to: u32,
}

fn test_value(test_number: u32, m: u32, n: u32, edges: &[Edge], expected_result: u32) -> u32 {
    let mut graph = BipartiteGraph::new(m, n);
    
    for edge in edges {
        if let Err(e) = graph.add_edge(edge.from, edge.to) {
            println!("{}", e);
            return 0;
        }
    }
    
    let result = graph.hopcroft_karp_algorithm();
    println!("Test {}: Result = {}, Expected = {}", test_number, result, expected_result);
    
    if result == expected_result {
        1
    } else {
        println!("Test {} failed.", test_number);
        0
    }
}

fn main() {
    println!("Running tests:");
    let mut success_count = 0;

    // Test Case 1
    success_count += test_value(1, 3, 5, &[Edge { from: 1, to: 4 }], 1);

    // Test Case 2
    success_count += test_value(2, 6, 6, &[
        Edge { from: 1, to: 4 }, 
        Edge { from: 1, to: 5 }, 
        Edge { from: 5, to: 1 }
    ], 2);

    // Test Case 3: Complete Bipartite Graph K(3, 3)
    let mut edges = Vec::new();
    for i in 1..=3 {
        for j in 1..=3 {
            edges.push(Edge { from: i, to: j });
        }
    }
    success_count += test_value(3, 3, 3, &edges, 3);

    // Test Case 4: No edges
    success_count += test_value(4, 2, 2, &[], 0);

    // Test Case 5
    let edges = vec![
        Edge { from: 1, to: 1 }, 
        Edge { from: 1, to: 3 }, 
        Edge { from: 2, to: 3 }, 
        Edge { from: 3, to: 4 }, 
        Edge { from: 4, to: 3 }, 
        Edge { from: 4, to: 2 }
    ];
    success_count += test_value(5, 4, 4, &edges, 4);

    if success_count == 5 {
        println!("All tests passed.");
    }
}
Output:
Running tests:
Test 1: Result = 1, Expected = 1
Test 2: Result = 2, Expected = 2
Test 3: Result = 3, Expected = 3
Test 4: Result = 0, Expected = 0
Test 5: Result = 4, Expected = 4
All tests passed.



Works with: Swift
Translation of: Python
import Foundation

// Setting a large value for infinity
let INF = Int.max
// Using 0 as the NIL node
let NIL = 0

class HKGraph {
    /**
     Implementation of the Hopcroft-Karp algorithm for finding maximum matching
     in a bipartite graph.
     
     Assumes vertices in the left partition (U) are numbered 1 to m,
     and vertices in the right partition (V) are numbered 1 to n.
     The NIL node is represented by 0.
     */
    
    private var m: Int  // Number of vertices on the left side (U)
    private var n: Int  // Number of vertices on the right side (V)
    
    // Adjacency list: adj[u] contains list of neighbors of u in V
    private var adj: [[Int]]
    
    // Matching pairs:
    // pair_u[u] stores the vertex v in V matched with u in U (or NIL if unmatched)
    private var pair_u: [Int]
    // pair_v[v] stores the vertex u in U matched with v in V (or NIL if unmatched)
    private var pair_v: [Int]
    
    // dist[u] stores the distance (level) of vertex u in U during BFS
    private var dist: [Int]
    
    /**
     Constructor for the HKGraph class.
     
     - Parameters:
         - m: Number of vertices in the left partition (U).
         - n: Number of vertices in the right partition (V).
     */
    init(_ m: Int, _ n: Int) {
        self.m = m
        self.n = n
        
        // Initialize with empty lists for vertices 1 to m
        self.adj = Array(repeating: [Int](), count: m + 1)
        
        // Initialize matching arrays
        self.pair_u = Array(repeating: NIL, count: m + 1)
        self.pair_v = Array(repeating: NIL, count: n + 1)
        
        // Initialize distance array
        self.dist = Array(repeating: INF, count: m + 1)
    }
    
    /**
     Adds a directed edge from vertex u (left partition) to vertex v (right partition).
     
     - Parameters:
         - u: Vertex index in the left partition (1 to m).
         - v: Vertex index in the right partition (1 to n).
     */
    func addEdge(_ u: Int, _ v: Int) {
        // Ensure vertices are within the valid range
        if 1 <= u && u <= self.m && 1 <= v && v <= self.n {
            self.adj[u].append(v)  // Add v to u's adjacency list
        } else {
            // Optionally print a warning for edges added outside the defined range
            // print("Warning: Attempted to add edge (\(u), \(v)) outside graph bounds [1..\(self.m)], [1..\(self.n)]")
        }
    }
    
    /**
     Performs Breadth-First Search (BFS) to find layers in the graph.
     It checks if there exists an augmenting path starting from a free vertex in U.
     
     - Returns: True if an augmenting path might exist (dist[NIL] is finite),
                False otherwise.
     */
    func bfs() -> Bool {
        var queue: [Int] = []
        
        // Initialize distances for vertices in U
        for u in 1...(self.m) {
            if self.pair_u[u] == NIL {
                // If u is a free vertex, its distance is 0, add to queue
                self.dist[u] = 0
                queue.append(u)
            } else {
                // Otherwise, set distance to infinity initially
                self.dist[u] = INF
            }
        }
        
        // Distance to the NIL node represents the length of the shortest augmenting path
        self.dist[NIL] = INF
        
        while !queue.isEmpty {
            let u = queue.removeFirst()  // Dequeue a vertex from U
            
            // If the path through u can potentially lead to a shorter augmenting path
            if self.dist[u] < self.dist[NIL] {
                // Explore neighbors v of u in V
                for v in self.adj[u] {
                    let matched_u = self.pair_v[v]  // Get the vertex u' matched with v
                    // If the matched vertex u' hasn't been visited yet (its distance is INF)
                    if self.dist[matched_u] == INF {
                        // Set the distance of u' based on u
                        self.dist[matched_u] = self.dist[u] + 1
                        // Enqueue u' to explore further
                        queue.append(matched_u)
                    }
                }
            }
        }
        
        // If dist[NIL] is still INF, no augmenting path was found originating
        // from the initial free vertices. Otherwise, augmenting paths might exist.
        return self.dist[NIL] != INF
    }
    
    /**
     Performs Depth-First Search (DFS) starting from vertex u in U
     to find and augment along a shortest path identified by BFS.
     
     - Parameter u: The current vertex in U being visited (or NIL).
     - Returns: True if an augmenting path was found and used starting from u,
                False otherwise.
     */
    func dfs(_ u: Int) -> Bool {
        if u != NIL {
            // Explore neighbors v of u in V
            for v in self.adj[u] {
                let matched_u = self.pair_v[v]  // Get the vertex u' matched with v
                // Check if the edge (u, v) leads to a vertex u'
                // such that the path u -> v -> u' is part of a shortest augmenting path
                if self.dist[matched_u] == self.dist[u] + 1 {
                    // Recursively call DFS on u'
                    if self.dfs(matched_u) {
                        // If an augmenting path is found starting from u',
                        // update the matching: match v with u, and u with v
                        self.pair_v[v] = u
                        self.pair_u[u] = v
                        return true  // Augmentation successful
                    }
                }
            }
            
            // If no augmenting path was found starting from u through any neighbor v,
            // mark u as visited in this DFS phase by setting its distance to INF
            self.dist[u] = INF
            return false  // Augmentation failed for this path
        }
        
        // Base case: If u is NIL, it means we have reached the end of an alternating path
        // originating from a free vertex in U and ending at a free vertex in V (represented by NIL).
        return true
    }
    
    /**
     Executes the Hopcroft-Karp algorithm to find the maximum matching.
     
     - Returns: The size of the maximum matching found.
     */
    func hopcroftKarpAlgorithm() -> Int {
        // Initialize matching pairs to NIL (unmatched)
        self.pair_u = Array(repeating: NIL, count: self.m + 1)
        self.pair_v = Array(repeating: NIL, count: self.n + 1)
        
        var matchingSize = 0  // Initialize the size of the matching
        
        // Keep finding augmenting paths using BFS and DFS until no more exist
        while self.bfs() {
            // For every free vertex u in U
            for u in 1...(self.m) {
                // If u is free and an augmenting path starting from u is found via DFS
                if self.pair_u[u] == NIL && self.dfs(u) {
                    // Increment the matching size
                    matchingSize += 1
                }
            }
        }
        
        return matchingSize
    }
}

// --- Testing ---

func tests() {
    print("Running tests...")
    
    // Test Case 1 (Corrected from C++ version - using 1-based indexing)
    // m=3, n=5, edges = [(1, 4)] - Expected matching size = 1
    let g1 = HKGraph(3, 5)
    g1.addEdge(1, 4)
    let res1 = g1.hopcroftKarpAlgorithm()
    let expected_res1 = 1
    print("Test 1: Result=\(res1), Expected=\(expected_res1)")
    assert(res1 == expected_res1, "Test 1 Failed: Expected \(expected_res1), got \(res1)")
    
    // Test Case 2 (Corrected from C++ version - using 1-based indexing, assuming (5,0) meant (5,1) or similar valid edge)
    // m=6, n=6, edges = [(1,4), (1,5), (5,1)]
    // Expected matching size = 2 (e.g., (1,4), (5,1) or (1,5), (5,1))
    // Note: Original C++ test had (0,1) and (5,0) which are problematic with 1-based index logic.
    let g2 = HKGraph(6, 6)
    // g3.addEdge(0,1) // Invalid vertex 0 in U
    g2.addEdge(1, 4)
    g2.addEdge(1, 5)
    g2.addEdge(5, 1)  // Assuming (5,0) meant a valid edge like (5,1)
    // g2.addEdge(5, 0) // Invalid vertex 0 in V
    let res2 = g2.hopcroftKarpAlgorithm()
    let expected_res2 = 2
    print("Test 2: Result=\(res2), Expected=\(expected_res2)")
    assert(res2 == expected_res2, "Test 2 Failed: Expected \(expected_res2), got \(res2)")
    
    // Test Case 3: Complete Bipartite Graph K_{3,3}
    // m=3, n=3, all possible edges. Expected matching size = 3
    let g3 = HKGraph(3, 3)
    for i in 1...3 {
        for j in 1...3 {
            g3.addEdge(i, j)
        }
    }
    let res3 = g3.hopcroftKarpAlgorithm()
    let expected_res3 = 3
    print("Test 3: Result=\(res3), Expected=\(expected_res3)")
    assert(res3 == expected_res3, "Test 3 Failed: Expected \(expected_res3), got \(res3)")
    
    // Test Case 4: No edges
    // m=2, n=2, no edges. Expected matching size = 0
    let g4 = HKGraph(2, 2)
    let res4 = g4.hopcroftKarpAlgorithm()
    let expected_res4 = 0
    print("Test 4: Result=\(res4), Expected=\(expected_res4)")
    assert(res4 == expected_res4, "Test 4 Failed: Expected \(expected_res4), got \(res4)")
    
    print("All tests passed!")
}

// --- Main execution ---

func main() {
    // Run self-tests first
    tests()
    print("\n--- Running main execution with hard-coded input ---")
    
    // --- Hard-coded input data ---
    // Example 1: Corresponds to Test Case 2
    let hardcoded_v1 = 4  // Number of vertices in left partition (m)
    let hardcoded_v2 = 4  // Number of vertices in right partition (n)
    let hardcoded_edges = [
        (1, 1),
        (1, 3),
        (2, 3),
        (3, 4),
        (4, 3),
        (4, 2)
    ]
    // Expected output for Example 1: 3
    
    // Use the selected hardcoded data
    let v1 = hardcoded_v1
    let v2 = hardcoded_v2
    let edges_data = hardcoded_edges
    let e = edges_data.count  // Number of edges is derived from the list
    
    // Create the graph object
    let g = HKGraph(v1, v2)
    
    print("Hard-coded graph dimensions: m=\(v1), n=\(v2), edges=\(e)")
    print("Adding hard-coded edges:")
    
    // Add edges from the hard-coded list
    for (u, v) in edges_data {
        print("  Adding edge: (\(u), \(v))")
        // Add edge only if vertices are within valid 1-based range
        if 1 <= u && u <= v1 && 1 <= v && v <= v2 {
            g.addEdge(u, v)
        } else {
            // This warning is important if hardcoded data might be invalid
            print("Warning: Skipping invalid hard-coded edge (\(u), \(v)) - indices out of range [1..\(v1)] or [1..\(v2)]")
        }
    }
    
    // Run the Hopcroft-Karp algorithm
    let max_matching_size = g.hopcroftKarpAlgorithm()
    
    // Print the result
    print("\nMaximum matching size is \(max_matching_size)")
}

// Run the main function
main()
Output:
Running tests...
Test 1: Result=1, Expected=1
Test 2: Result=2, Expected=2
Test 3: Result=3, Expected=3
Test 4: Result=0, Expected=0
All tests passed!

--- Running main execution with hard-coded input ---
Hard-coded graph dimensions: m=4, n=4, edges=6
Adding hard-coded edges:
  Adding edge: (1, 1)
  Adding edge: (1, 3)
  Adding edge: (2, 3)
  Adding edge: (3, 4)
  Adding edge: (4, 3)
  Adding edge: (4, 2)

Maximum matching size is 4


Works with: Tcl version 8.6
Translation of: Perl
#!/usr/bin/env tclsh

# Representation of a bipartite graph
# Vertices in the left partition, U, are numbered from 1 to m,
# and vertices in the right partition, V, are numbered 1 to n.

namespace eval BipartiteGraph {
    namespace export new add_edge hopcroft_karp_algorithm
}

proc BipartiteGraph::new {m n} {
    set self [dict create]
    dict set self m $m
    dict set self n $n
    dict set self adjacency_lists [dict create]
    dict set self pair_u [dict create]
    dict set self pair_v [dict create]
    dict set self levels [dict create]
    dict set self NIL 0
    dict set self INFINITY 999999999
    
    # Initialize adjacency lists
    for {set u 1} {$u <= $m} {incr u} {
        dict set self adjacency_lists $u [list]
    }
    
    # Initialize pairs
    for {set u 1} {$u <= $m} {incr u} {
        dict set self pair_u $u [dict get $self NIL]
    }
    for {set v 1} {$v <= $n} {incr v} {
        dict set self pair_v $v [dict get $self NIL]
    }
    
    # Initialize levels
    for {set u 1} {$u <= $m} {incr u} {
        dict set self levels $u [dict get $self INFINITY]
    }
    
    return $self
}

proc BipartiteGraph::add_edge {self_ref u v} {
    upvar $self_ref self
    
    set m [dict get $self m]
    set n [dict get $self n]
    
    if {$u >= 1 && $u <= $m && $v >= 1 && $v <= $n} {
        set adj_lists [dict get $self adjacency_lists]
        set u_list [dict get $adj_lists $u]
        lappend u_list $v
        dict set adj_lists $u $u_list
        dict set self adjacency_lists $adj_lists
    } else {
        error "Attempt to add an edge ($u, $v) which is out of bounds"
    }
}

# Return the matching size of the bipartite graph
proc BipartiteGraph::hopcroft_karp_algorithm {self_ref} {
    upvar $self_ref self
    
    set m [dict get $self m]
    set n [dict get $self n]
    set NIL [dict get $self NIL]
    set INFINITY [dict get $self INFINITY]
    
    # Reset pairs
    for {set u 1} {$u <= $m} {incr u} {
        dict set self pair_u $u $NIL
    }
    for {set v 1} {$v <= $n} {incr v} {
        dict set self pair_v $v $NIL
    }
    
    set matching_size 0
    
    while {[breadth_first_search self]} {
        for {set u 1} {$u <= $m} {incr u} {
            set pair_u_dict [dict get $self pair_u]
            
            if {[dict get $pair_u_dict $u] == $NIL && [depth_first_search self $u]} {
                # vertex u is free and an augmenting path starting
                # from u has been found by the depth first search
                incr matching_size
            }
        }
    }
    
    return $matching_size
}

# Determines whether there exists an augmenting path starting from a free vertex in U.
# Return true if an augmenting path could exist, otherwise false.
proc BipartiteGraph::breadth_first_search {self_ref} {
    upvar $self_ref self
    
    set m [dict get $self m]
    set n [dict get $self n]
    set NIL [dict get $self NIL]
    set INFINITY [dict get $self INFINITY]
    
    set queue [list]
    
    # Initialize 'levels' for the vertices in U
    for {set u 1} {$u <= $m} {incr u} {
        set pair_u_dict [dict get $self pair_u]
        
        if {[dict get $pair_u_dict $u] == $NIL} {
            # If u is a free vertex, its level is 0 and it is added to the queue
            dict set self levels $u 0
            lappend queue $u
        } else {
            # Otherwise, set 'levels' to infinity
            dict set self levels $u $INFINITY
        }
    }
    
    # The 'level' to the NIL node represents the length of the shortest augmenting path
    dict set self levels $NIL $INFINITY
    
    while {[llength $queue] > 0} {
        set u [lindex $queue 0]
        set queue [lrange $queue 1 end]
        
        set levels_dict [dict get $self levels]
        if {[dict get $levels_dict $u] < [dict get $levels_dict $NIL]} {
            # The path through u could lead to a shorter augmenting path
            set adj_lists [dict get $self adjacency_lists]
            set u_list [dict get $adj_lists $u]
            set pair_v_dict [dict get $self pair_v]
            
            foreach v $u_list {
                # Explore the neighbours v of u in V
                set matched_u [dict get $pair_v_dict $v]
                set levels_dict [dict get $self levels]
                if {[dict get $levels_dict $matched_u] == $INFINITY} {
                    # The matched vertex has not been visited yet
                    dict set self levels $matched_u [expr {[dict get $levels_dict $u] + 1}]
                    lappend queue $matched_u ;# Enqueue the matched vertex to explore it further
                }
            }
        }
    }
    
    # An augmenting path from the initial free vertices was found if levels[NIL] is not INFINITY
    set levels_dict [dict get $self levels]
    return [expr {[dict get $levels_dict $NIL] != $INFINITY}]
}

# Determine whether the shortest path from vertex u in U found by breadth_first_search() can be augmented.
# Return true if an augmenting path was found starting from u, otherwise false.
proc BipartiteGraph::depth_first_search {self_ref u} {
    upvar $self_ref self
    
    set NIL [dict get $self NIL]
    set INFINITY [dict get $self INFINITY]
    
    if {$u != $NIL} {
        set adj_lists [dict get $self adjacency_lists]
        set u_list [dict get $adj_lists $u]
        set pair_v_dict [dict get $self pair_v]
        set levels_dict [dict get $self levels]
        
        foreach v $u_list {
            # Explore neighbours v of u in V
            set matched_u [dict get $pair_v_dict $v]
            # Check whether the edge (u, v) leads to a vertex matched_u
            # such that the path u -> v -> matched_u is part of a shortest augmenting path
            if {[dict get $levels_dict $matched_u] == [expr {[dict get $levels_dict $u] + 1}]} {
                if {[depth_first_search self $matched_u]} {
                    # An augmenting path is found starting from 'matched_u'
                    dict set self pair_v $v $u ;# Match v with u,
                    dict set self pair_u $u $v ;# and u with v
                    return 1
                }
            }
        }
        
        # No augmenting path was found starting from vertex u through any of its neighbours v,
        # so remove u from the depth first search phase of the algorithm
        dict set self levels $u $INFINITY
        return 0
    }
    
    return 1
}

proc test_value {test_number m n edges expected_result} {
    set graph [BipartiteGraph::new $m $n]
    
    foreach edge $edges {
        set from_val [dict get $edge from]
        set to_val [dict get $edge to]
        BipartiteGraph::add_edge graph $from_val $to_val
    }
    
    set result [BipartiteGraph::hopcroft_karp_algorithm graph]
    puts "Test $test_number: Result = $result, Expected = $expected_result"
    
    if {$result == $expected_result} {
        return 1
    }
    
    puts "Test $test_number failed."
    return 0
}

# Main execution
puts "Running tests:"
set success_count 0

# Test Case 1
set edges1 [list [dict create from 1 to 4]]
incr success_count [test_value 1 3 5 $edges1 1]

# Test Case 2
set edges2 [list \
    [dict create from 1 to 4] \
    [dict create from 1 to 5] \
    [dict create from 5 to 1] \
]
incr success_count [test_value 2 6 6 $edges2 2]

# Test Case 3: Complete Bipartite Graph K(3, 3)
set edges3 [list]
for {set i 1} {$i <= 3} {incr i} {
    for {set j 1} {$j <= 3} {incr j} {
        lappend edges3 [dict create from $i to $j]
    }
}
incr success_count [test_value 3 3 3 $edges3 3]

# Test Case 4: No edges
incr success_count [test_value 4 2 2 [list] 0]

# Test Case 5
set edges5 [list \
    [dict create from 1 to 1] \
    [dict create from 1 to 3] \
    [dict create from 2 to 3] \
    [dict create from 3 to 4] \
    [dict create from 4 to 3] \
    [dict create from 4 to 2] \
]
incr success_count [test_value 5 4 4 $edges5 4]

if {$success_count == 5} {
    puts "All tests passed."
}
Output:
Running tests:
Test 1: Result = 1, Expected = 1
Test 2: Result = 2, Expected = 2
Test 3: Result = 3, Expected = 3
Test 4: Result = 0, Expected = 0
Test 5: Result = 4, Expected = 4
All tests passed.



Translation of: Python
Library: Wren-queue
import "./queue" for Deque

// Setting a large value for infinity.
var INF = Num.maxSafeInteger

// Using 0 as the NIL code
var NIL = 0

/* Implementation of the Hopcroft-Karp algorithm for finding maximum matching
   in a bipartite graph.

   Assumes vertices in the left partition (U) are numbered 1 to m,
   and vertices in the right partition (V) are numbered 1 to n.
   The NIL node is represented by 0. */
class HKGraph {
   // Constructs a new HKGraph object with m vertices in the left partition (U)
   // and n vertices in the right partition (V).
   construct new(m, n) {
        _m = m
        _n = n

        // Adjacency list: _adj[u] contains list of neighbors of u in V
        // Initialize with empty lists for vertices 1 to m.
        _adj = List.filled(m+1, null)
        for (i in 1..m) _adj[i] = []

        // Matching pairs:
        // pairU[u] stores the vertex v in V matched with u in U (or NIL if unmatched).
        _pairU = List.filled(m+1, NIL)
        // pairV[v] stores the vertex u in U matched with v in V (or NIL if unmatched).
        _pairV = List.filled(n+1, NIL)

        // _dist[u] stores the distance (level) of vertex u in U during BFS.
        // Initialized within the hopcroftKarp or bfs methods.
        _dist = List.filled(m+1, INF)
    }

    // Adds a directed edge from vertex u (left partition) to vertex v (right partition).
    addEdge(u, v) {
        // Ensure vertices are within the valid range.
        if (1 <= u && u <= _m && 1 <= v && v <= _n) {
            _adj[u].add(v)  // add v to u's adjacency list
        } else {
            System.print("Warning: Attempted to add edge (%(u), %(v))" +
                        " outside graph bounds [1..%(m)], [1..%(n)]")
        }
    }

    // Performs Breadth-First Search (BFS) to find layers in the graph.
    // It checks if there exists an augmenting path starting from a free vertex in U.
    // Returns true if an augmenting path might exist (_dist[NIL] is finite),
    // false otherwise.
    bfs() {
        var queue = Deque.new()  // use deque for efficient queue operations

        // Initialize distances for vertices in U.
        for (u in 1.._m) {
            if (_pairU[u] == NIL) {
                // If u is a free vertex, its distance is 0, add to queue.
                _dist[u] = 0
                queue.pushBack(u)
            } else {
                // Otherwise, set distance to infinity initially.
                _dist[u] = INF
            }
        }

        // Distance to the NIL node represents the length of the shortest augmenting path.
        _dist[NIL] = INF

        while (!queue.isEmpty) {
            var u = queue.popFront()  // dequeue a vertex from U

            // If the path through u can potentially lead to a shorter augmenting path:
            if (_dist[u] < _dist[NIL]) {
                // Explore neighbors v of u in V.
                for (v in _adj[u]) {
                    var matchedU = _pairV[v]  // get the vertex u' matched with v
                    // If the matched vertex u' hasn't been visited yet (its distance is INF):
                    if (_dist[matchedU] == INF) {
                        // Set the distance of u' based on u.
                        _dist[matchedU] = _dist[u] + 1
                        // Enqueue u' to explore further.
                        queue.pushBack(matchedU)
                    }
                }
            }
        }

        // If _dist[NIL] is still INF, no augmenting path was found originating
        // from the initial free vertices. Otherwise, augmenting paths might exist.
        return _dist[NIL] != INF
    }

    // Performs Depth-First Search (DFS) starting from vertex u in U
    // to find and augment along a shortest path identified by BFS.
    // Returns true if an augmenting path was found and used starting from u,
    // false otherwise.
    dfs(u) {
        if (u != NIL) {
            // Explore neighbors v of u in V.
            for (v in _adj[u]) {
                var matchedU = _pairV[v]  // get the vertex u' matched with v
                // Check if the edge (u, v) leads to a vertex u'
                // such that the path u -> v -> u' is part of a shortest augmenting path.
                if (_dist[matchedU] == _dist[u] + 1) {
                    // Recursively call DFS on u'.
                    if (dfs(matchedU)) {
                        // If an augmenting path is found starting from u',
                        // update the matching: match v with u, and u with v.
                        _pairV[v] = u
                        _pairU[u] = v
                        return true  // augmentation successful
                    }
                }
            }

            // If no augmenting path was found starting from u through any neighbor v,
            // mark u as visited in this DFS phase by setting its distance to INF.
            _dist[u] = INF
            return false  // augmentation failed for this path
        }

        // Base case: If u is NIL, it means we have reached the end of an alternating path
        // originating from a free vertex in U and ending at a free vertex in V (represented by NIL).
        return true
    }

    // Executes the Hopcroft-Karp algorithm to find the maximum matching and returns its size.
    hopcroftKarp() {
        // Initialize matching pairs to NIL (unmatched).
        _pairU = List.filled(_m+1, NIL)
        _pairV = List.filled(_n+1, NIL)

        var matchingSize = 0  // initialize the size of the matching

        // Keep finding augmenting paths using BFS and DFS until no more exist.
        while (bfs()) {
            // For every free vertex u in U:
            for (u in 1.._m) {
                // If u is free and an augmenting path starting from u is found via DFS:
                if (_pairU[u] == NIL && dfs(u)) {
                    // Increment the matching size.
                    matchingSize = matchingSize + 1
                }
            }
        }

        return matchingSize
    }
}

// --- Testing ---

// Runs test cases for the Hopcroft-Karp implementation.
var tests = Fn.new {
    System.print("Running tests...")
    var success = 0

    // Test Case 1
    // m=3, n=5, edges = [(1, 4)] - Expected matching size = 1.
    var g1 = HKGraph.new(3, 5)
    g1.addEdge(1, 4)
    var res1 = g1.hopcroftKarp()
    var expectedRes1 = 1
    System.print("Test 1: Result = %(res1), Expected = %(expectedRes1)")
    if (res1 != expectedRes1) System.print("So test 1 Failed.") else success = success + 1

    // Test Case 2
    // m=6, n=6, edges = [(1,4), (1,5), (5,1)]
    // Expected matching size = 2 (e.g., (1,4), (5,1) or (1,5), (5,1)).
    var g2 = HKGraph.new(6, 6)
    g2.addEdge(1, 4)
    g2.addEdge(1, 5)
    g2.addEdge(5, 1)
    var res2 = g2.hopcroftKarp()
    var expectedRes2 = 2
    System.print("Test 2: Result = %(res2), Expected = %(expectedRes2)")
    if (res2 != expectedRes2) System.print("So test 2 failed") else success = success + 1

    // Test Case 3: Complete Bipartite Graph K_{3,3}
    // m=3, n=3, all possible edges. Expected matching size = 3.
    var g3 = HKGraph.new(3, 3)
    for (i in 1..3) {
        for (j in 1..3) g3.addEdge(i, j)
    }
    var res3 = g3.hopcroftKarp()
    var expectedRes3 = 3
    System.print("Test 3: Result = %(res3), Expected = %(expectedRes3)")
    if (res3 != expectedRes3) System.print("So test 3 failed") else success = success + 1

    // Test Case 4: No edges
    // m=2, n=2, no edges. Expected matching size = 0.
    var g4 = HKGraph.new(2, 2)
    var res4 = g4.hopcroftKarp()
    var expectedRes4 = 0
    System.print("Test 4: Result = %(res4), Expected = %(expectedRes4)")
    if (res4 != expectedRes4) System.print("So test 4 Failed") else success = success + 1

    if (success == 4) System.print("All tests passed!")
}

// --- Main execution ---

// Run self-tests first.
tests.call()

System.print("\n--- Running main execution with hard-coded input ---")

// --- Hard-coded input data ---
// Example 1: Corresponds to Test Case 2.
var hardcodedV1 = 4  // mumber of vertices in left partition (m)
var hardcodedV2 = 4  // number of vertices in right partition (n)
var hardcodedEdges = [ [1, 1], [1, 3], [2, 3], [3, 4], [4, 3], [4, 2] ]
// Expected output for Example 1: 3.

// Use the selected hardcoded data.
var v1 = hardcodedV1
var v2 = hardcodedV2
var edgesData = hardcodedEdges
var e = edgesData.count  // number of edges is derived from the list

// Create the graph object.
var g = HKGraph.new(v1, v2)

System.print("Hard-coded graph dimensions: m=%(v1), n=%(v2), edges=%(e)")
System.print("Adding hard-coded edges:")

// Add edges from the hard-coded list.
for (edge in edgesData) {
    var u = edge[0]
    var v = edge[1]
    System.print("  Adding edge: (%(u), %(v))")
    // Add edge only if vertices are within valid 1-based range.
    if (1 <= u && u <= v1 && 1 <= v && v <= v2) {
        g.addEdge(u, v)
    } else {
        // This warning is important if hardcoded data might be invalid.
        System.print("Warning: Skipping invalid hard-coded edge (%(u), %(v)) - " +
                     "indices out of range [1..%(v1)] or [1..%(v2)]")
    }
}
// Run the Hopcroft-Karp algorithm.
var maxMatchingSize = g.hopcroftKarp()

// Print the result.
System.print("\nMaximum matching size is %(maxMatchingSize)")
Output:
Running tests...
Test 1: Result = 1, Expected = 1
Test 2: Result = 2, Expected = 2
Test 3: Result = 3, Expected = 3
Test 4: Result = 0, Expected = 0
All tests passed!

--- Running main execution with hard-coded input ---
Hard-coded graph dimensions: m=4, n=4, edges=6
Adding hard-coded edges:
  Adding edge: (1, 1)
  Adding edge: (1, 3)
  Adding edge: (2, 3)
  Adding edge: (3, 4)
  Adding edge: (4, 3)
  Adding edge: (4, 2)

Maximum matching size is 4


Works with: Zig version 0.14.0
Translation of: C++
const std = @import("std");

/// Representation of a bipartite graph.
/// Vertices in the left partition, U, are numbered from 1 to m,
/// and vertices in the right partition, V, are numbered 1 to n.
const BipartiteGraph = struct {
    adjacency_lists: std.ArrayList(std.ArrayList(u32)),
    pair_u: std.ArrayList(u32),
    pair_v: std.ArrayList(u32),
    levels: std.ArrayList(u32),
    m: u32,
    n: u32,
    allocator: std.mem.Allocator,
    
    const NIL: u32 = 0;
    const INFINITY: u32 = std.math.maxInt(u32);
    
    pub fn init(allocator: std.mem.Allocator, m: u32, n: u32) !BipartiteGraph {
        var adjacency_lists = std.ArrayList(std.ArrayList(u32)).init(allocator);
        try adjacency_lists.resize(m + 1);
        for (0..m + 1) |i| {
            adjacency_lists.items[i] = std.ArrayList(u32).init(allocator);
        }
        
        var pair_u = try std.ArrayList(u32).initCapacity(allocator, m + 1);
        try pair_u.appendNTimes(NIL, m + 1);
        
        var pair_v = try std.ArrayList(u32).initCapacity(allocator, n + 1);
        try pair_v.appendNTimes(NIL, n + 1);
        
        var levels = try std.ArrayList(u32).initCapacity(allocator, m + 1);
        try levels.appendNTimes(INFINITY, m + 1);
        
        return BipartiteGraph{
            .adjacency_lists = adjacency_lists,
            .pair_u = pair_u,
            .pair_v = pair_v,
            .levels = levels,
            .m = m,
            .n = n,
            .allocator = allocator,
        };
    }
    
    pub fn deinit(self: *BipartiteGraph) void {
        for (0..self.adjacency_lists.items.len) |i| {
            self.adjacency_lists.items[i].deinit();
        }
        self.adjacency_lists.deinit();
        self.pair_u.deinit();
        self.pair_v.deinit();
        self.levels.deinit();
    }
    
    pub fn add_edge(self: *BipartiteGraph, u: u32, v: u32) !void {
        if (1 <= u and u <= self.m and 1 <= v and v <= self.n) {
            try self.adjacency_lists.items[u].append(v);
        } else {
            return error.InvalidEdge;
        }
    }
    
    /// Return the matching size of the bipartite graph.
    pub fn hopcroftKarp_algorithm(self: *BipartiteGraph) !u32 {
        var matching_size: u32 = 0;
        
        // Reset the matching
        for (0..self.pair_u.items.len) |i| {
            self.pair_u.items[i] = NIL;
        }
        for (0..self.pair_v.items.len) |i| {
            self.pair_v.items[i] = NIL;
        }
        
        while (try self.breadth_first_search()) {
            var u: u32 = 1;
            while (u <= self.m) : (u += 1) {
                if (self.pair_u.items[u] == NIL and try self.depth_first_search(u)) {
                    matching_size += 1;
                }
            }
        }
        
        return matching_size;
    }
    
    /// Determines whether there exists an augmenting path starting from a free vertex in U.
    ///
    /// Return true if an augmenting path could exist, otherwise false.
    fn breadth_first_search(self: *BipartiteGraph) !bool {
        var queue = std.fifo.LinearFifo(u32, .Dynamic).init(self.allocator);
        defer queue.deinit();
        
        // Initialize 'levels' for the vertices in U
        var u: u32 = 1;
        while (u <= self.m) : (u += 1) {
            if (self.pair_u.items[u] == NIL) {
                // If u is a free vertex, its level is 0 and it is added to the queue
                self.levels.items[u] = 0;
                try queue.writeItem(u);
            } else {
                // Otherwise, set 'levels' to infinity
                self.levels.items[u] = INFINITY;
            }
        }
        
        // The 'level' to the NIL node represents the length of the shortest augmenting path
        self.levels.items[NIL] = INFINITY;
        
        while (queue.readItem()) |my_u| {
            if (self.levels.items[my_u] < self.levels.items[NIL]) {
                // The path through u could lead to a shorter augmenting path
                for (self.adjacency_lists.items[my_u].items) |v| {
                    const matched_u = self.pair_v.items[v];
                    if (self.levels.items[matched_u] == INFINITY) {
                        // The matched vertex has not been visited yet
                        self.levels.items[matched_u] = self.levels.items[my_u] + 1;
                        try queue.writeItem(matched_u);
                    }
                }
            }
        }
        
        // An augmenting path from the initial free vertices was found if levels[NIL] is not INFINITY
        return self.levels.items[NIL] != INFINITY;
    }
    
    /// Determine whether the shortest path from vertex u in U found by breadth_first_search() can be augmented.
    ///
    /// Return true if an augmenting path was found starting from u, otherwise false.
    fn depth_first_search(self: *BipartiteGraph, u: u32) !bool {
        if (u != NIL) {
            // Explore neighbours v of u in V
            for (self.adjacency_lists.items[u].items) |v| {
                const matched_u = self.pair_v.items[v];
                // Check whether the edge (u, v) leads to a vertex matched_u
                // such that the path u -> v -> matched_u is part of a shortest augmenting path
                if (self.levels.items[matched_u] == self.levels.items[u] + 1) {
                    if (try self.depth_first_search(matched_u)) {
                        // An augmenting path is found starting from 'matched_u'
                        self.pair_v.items[v] = u; // Match v with u
                        self.pair_u.items[u] = v; // and u with v
                        return true;
                    }
                }
            }
            
            // No augmenting path was found starting from vertex u through any of its neighbours v,
            // so remove u from the depth first search phase of the algorithm
            self.levels.items[u] = INFINITY;
            return false;
        }
        
        return true;
    }
};

const Edge = struct {
    from: u32,
    to: u32,
};

fn test_value(allocator: std.mem.Allocator, test_number: u32, m: u32, n: u32, edges: []const Edge, expected_result: u32) !u32 {
    var graph = try BipartiteGraph.init(allocator, m, n);
    defer graph.deinit();
    
    for (edges) |edge| {
        try graph.add_edge(edge.from, edge.to);
    }
    
    const result = try graph.hopcroftKarp_algorithm();
    
    const stdout = std.io.getStdOut().writer();
    try stdout.print("Test {d}: Result = {d}, Expected = {d}\n", .{ test_number, result, expected_result });
    
    if (result == expected_result) {
        return 1;
    }
    
    try stdout.print("Test {d} failed.\n", .{test_number});
    return 0;
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    const stdout = std.io.getStdOut().writer();
    try stdout.print("Running tests:\n", .{});
    var success_count: u32 = 0;
    
    // Test Case 1
    {
        const edges = [_]Edge{.{ .from = 1, .to = 4 }};
        success_count += try test_value(allocator, 1, 3, 5, &edges, 1);
    }
    
    // Test Case 2
    {
        const edges = [_]Edge{
            .{ .from = 1, .to = 4 },
            .{ .from = 1, .to = 5 },
            .{ .from = 5, .to = 1 },
        };
        success_count += try test_value(allocator, 2, 6, 6, &edges, 2);
    }
    
    // Test Case 3: Complete Bipartite Graph K(3, 3)
    {
        var edges = std.ArrayList(Edge).init(allocator);
        defer edges.deinit();
        var i: u32 = 1;
        while (i <= 3) : (i += 1) {
            var j: u32 = 1;
            while (j <= 3) : (j += 1) {
                try edges.append(.{ .from = i, .to = j });
            }
        }
        success_count += try test_value(allocator, 3, 3, 3, edges.items, 3);
    }
    
    // Test Case 4: No edges
    {
        const edges = [_]Edge{};
        success_count += try test_value(allocator, 4, 2, 2, &edges, 0);
    }
    
    // Test Case 5
    {
        const edges = [_]Edge{
            .{ .from = 1, .to = 1 },
            .{ .from = 1, .to = 3 },
            .{ .from = 2, .to = 3 },
            .{ .from = 3, .to = 4 },
            .{ .from = 4, .to = 3 },
            .{ .from = 4, .to = 2 },
        };
        success_count += try test_value(allocator, 5, 4, 4, &edges, 4);
    }
    
    if (success_count == 5) {
        try stdout.print("All tests passed.\n", .{});
    }
}
Output:
Running tests:
Test 1: Result = 1, Expected = 1
Test 2: Result = 2, Expected = 2
Test 3: Result = 3, Expected = 3
Test 4: Result = 0, Expected = 0
Test 5: Result = 4, Expected = 4
All tests passed.