UNPKG

randomized-hopcroft-karp

Version:

An algorithm for maximum cardinality matching of bipartite graph

123 lines (122 loc) 4.05 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const inf = 0x7fffffff; const nil = -1; function findMatching(m, // number of nodes U n, // number of nodes V edges, // edges between U and V groups // groups which nodes U belong to. The algorithm tries to maximize cardinality of chosen groups. ) { const adj = new Array(m).fill(undefined).map(() => []); edges.forEach(([u, v]) => { adj[u].push(v); }); const uPair = new Int32Array(m).fill(nil); const vPair = new Int32Array(n).fill(nil); const dists = new Int32Array(m + 1); const bfs = () => { const queue = []; for (let u = 0; u < m; u++) { if (uPair[u] === nil) { dists[u + 1] = 0; queue.push(u); } else { dists[u + 1] = inf; } } dists[nil + 1] = inf; for (let u; (u = queue.shift()) !== undefined;) { if (dists[u + 1] < dists[nil + 1]) { for (const v of adj[u]) { if (dists[vPair[v] + 1] === inf) { dists[vPair[v] + 1] = dists[u + 1] + 1; queue.push(vPair[v]); } } } } return dists[nil + 1] !== inf; }; const dfs = (u) => { if (u !== nil) { for (const v of shuffle(adj[u])) { if (dists[vPair[v] + 1] === dists[u + 1] + 1) { if (dfs(vPair[v])) { vPair[v] = u; uPair[u] = v; return true; } } } dists[u + 1] = inf; return false; } return true; }; if (groups === undefined) { while (bfs()) { for (const u of shuffle(Array(m).keys())) { if (uPair[u] === nil) { dfs(u); } } } } else { let gsByCount = [new Set(groups)]; const remainingUs = new Set(Array(m).keys()); while (bfs()) { let scheduledGs; let scheduledUs = []; let lastCount = -1; const unvisitedUsByGroup = Object.fromEntries([...new Set(groups)].map((g) => [g, new Set()])); for (const u of remainingUs) { unvisitedUsByGroup[groups[u]].add(u); } const pop = () => { let u; while ((u = scheduledUs.shift()) === undefined || scheduledGs.size === 0) { lastCount += 1; const gs = gsByCount[lastCount]; if (gs === undefined) { return undefined; } scheduledUs = shuffle([...gs].flatMap((g) => [...unvisitedUsByGroup[g]])); scheduledGs = gs; } if (scheduledGs.has(groups[u])) { unvisitedUsByGroup[groups[u]].delete(u); return u; } }; const match = (u) => { var _a; const g = groups[u]; gsByCount[_a = lastCount + 1] ?? (gsByCount[_a] = new Set()); gsByCount[lastCount + 1].add(g); gsByCount[lastCount].delete(g); scheduledGs.delete(g); remainingUs.delete(u); }; for (let u; (u = pop()) !== undefined;) { if (dfs(u)) { match(u); } } } } return Array.from(uPair) .map((v, u) => [u, v]) .filter(([u, v]) => v !== nil); } exports.default = findMatching; const shuffle = (iter) => { const array = [...iter]; for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; };