maximum-matching
Version:
Implementation of Blossom's Algorithm for Maximum Matching
101 lines (100 loc) • 4.36 kB
JavaScript
"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);
}
}