// Hopcroft–Karp for bipartite matching
// L side: 0..(nL-1), R side: 0..(nR-1); adjL[u] lists neighbors v in R
function hopcroftKarp(nL, nR, adjL) {
const NIL = -1;
const pairU = Array(nL).fill(NIL);
const pairV = Array(nR).fill(NIL);
const dist = Array(nL).fill(0);
function bfs() {
const q = [];
for (let u = 0; u < nL; u++) {
if (pairU[u] === NIL) { dist[u] = 0; q.push(u); }
else dist[u] = Infinity;
}
let reachableFree = false;
for (let qi = 0; qi < q.length; qi++) {
const u = q[qi];
for (const v of adjL[u]) {
const u2 = pairV[v];
if (u2 !== NIL && dist[u2] === Infinity) { dist[u2] = dist[u] + 1; q.push(u2); }
if (u2 === NIL) reachableFree = true;
}
}
return reachableFree;
}
function dfs(u) {
for (const v of adjL[u]) {
const u2 = pairV[v];
if (u2 === NIL || (dist[u2] === dist[u] + 1 && dfs(u2))) {
pairU[u] = v; pairV[v] = u; return true;
}
}
dist[u] = Infinity; // mark as dead-end in this layer graph
return false;
}
let matching = 0;
while (bfs()) {
for (let u = 0; u < nL; u++) if (pairU[u] === NIL && dfs(u)) matching++;
}
return { matching, pairU, pairV };
}
- Idea: Layered BFS builds the level graph; DFS finds a maximal set of vertex-disjoint shortest augmenting paths per phase.
- Use cases: job assignment (unweighted), scheduling, pairing tasks, team formation.