UNPKG

maximum-matching

Version:

Implementation of Blossom's Algorithm for Maximum Matching

101 lines (100 loc) 4.36 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.findAugmentingPath = void 0; const Forest_1 = require("../../graphs/Forest"); const MatchingGraph_1 = require("./MatchingGraph"); const Blossom_1 = require("../blossoms/Blossom"); function findAugmentingPath(graph) { return new AugmentingPathFinder(graph).find(); } exports.findAugmentingPath = findAugmentingPath; class AugmentingPathFinder { constructor(graph) { this.graph = graph; this.toCheck = graph.unpairedNodes(); this.forest = Forest_1.Forest.fromRoots(this.toCheck); } find() { let currentNode; while ((currentNode = this.pickNextNodeToCheck())) { for (const neighbor of this.graph.neighborsThroughUnpairedEdges(currentNode)) { const { augmentingPath, blossom } = this.tryToConnectWithAugmentingPath(currentNode, neighbor); if (augmentingPath) return augmentingPath; if (blossom) return this.findHavingBlossom(blossom); } } return undefined; } pickNextNodeToCheck() { return this.toCheck.shift(); } tryToConnectWithAugmentingPath(currentNode, neighbor) { if (!this.forest.has(neighbor)) { this.visitNodeOutsideForest(currentNode, neighbor); return {}; } if (!this.areConnectableWithAugmentingPath(currentNode, neighbor)) return {}; if (this.forest.areInTheSameTree(currentNode, neighbor)) return { blossom: this.buildBlossom(currentNode, neighbor) }; return { augmentingPath: this.connectWithAugmentingPath(currentNode, neighbor) }; } visitNodeOutsideForest(currentNode, outsider) { const outsiderMate = this.graph.getMateOrFail(outsider); this.forest.findSubtreeOrFail(currentNode).addChild(outsider).addChild(outsiderMate); this.checkLater(outsiderMate); } checkLater(node) { this.toCheck.push(node); } areConnectableWithAugmentingPath(_currentNode, neighbor) { return this.forest.distanceToItsRootTree(neighbor) % 2 === 0; } buildBlossom(currentNode, neighbor) { const currentNodeRootPath = this.forest.pathToItsRootTree(currentNode); const neighborRootPath = this.forest.pathToItsRootTree(neighbor); const intersectionRootPath = currentNodeRootPath.filter((node) => neighborRootPath.includes(node)); const [blossomRoot] = intersectionRootPath; const cycle = [ ...currentNodeRootPath.slice(0, currentNodeRootPath.indexOf(blossomRoot)), blossomRoot, ...neighborRootPath.slice(0, neighborRootPath.indexOf(blossomRoot)).reverse(), ]; return new Blossom_1.Blossom({ cycle, root: blossomRoot }); } connectWithAugmentingPath(currentNode, neighbor) { return [ ...this.forest.pathToItsRootTree(currentNode).reverse(), ...this.forest.pathToItsRootTree(neighbor), ]; } findHavingBlossom(blossom) { const graphWithoutBlossom = MatchingGraph_1.MatchingGraph.createCompressing(this.graph, blossom); const augmentingPath = findAugmentingPath(graphWithoutBlossom); if (!augmentingPath) return undefined; const superNodeIndex = augmentingPath.findIndex((node) => graphWithoutBlossom.isSuperNode(node)); if (superNodeIndex < 0) return augmentingPath; return [ ...augmentingPath.slice(0, superNodeIndex), ...this.expandBlossom({ blossom, previousNode: augmentingPath[superNodeIndex - 1], nextNode: augmentingPath[superNodeIndex + 1], }), ...augmentingPath.slice(superNodeIndex + 1), ]; } expandBlossom(params) { const { blossom, previousNode, nextNode } = params; const { root, cycle } = blossom; const rootMate = this.graph.getMate(root); const connectionFor = (node) => !node || node === rootMate ? root : this.graph.findNeighborOrFail({ for: node, in: cycle }); const expandedStart = connectionFor(previousNode); const expandedEnd = connectionFor(nextNode); return blossom.evenPath(expandedStart, expandedEnd); } }