// TODO: Factor out to common module Array.prototype.equals = function (other) { if (this.length != other.length) { return false; } let i = this.length; while (--i >= 0) { if (this[i] !== other[i]) { return false; } } return true; }; function balanceFactor(node) { if (!node) { return 0; } let [left, right] = [nodeRank(node.left), nodeRank(node.right)]; return right - left; } function updateRank(node) { let [left, right] = [nodeRank(node.left), nodeRank(node.right)]; node.rank = 1 + Math.max(left, right); } class AVLTree extends RankedTree { isCorrectNode(node, recursive) { if (!node) { return true; } recursive = recursive ?? true; let differences = nodeDifferences(node); differences.sort(); if (!differences.equals([1, 1]) && !differences.equals([1, 2])) { return false; } return ( !recursive || (this.isCorrectNode(node.left) && this.isCorrectNode(node.right)) ); } fix0Child(x, y, z, rotatingAroundRoot, rotateLeft, rotateRight) { let newRoot = x.parent; if (!y || nodeDifference(y) == 2) { newRoot = rotateRight(z); if (rotatingAroundRoot) { this.root = newRoot; } this.record(); z.demote(); this.record(); } else if (nodeDifference(y) == 1) { rotateLeft(x); this.record(); newRoot = rotateRight(z); if (rotatingAroundRoot) { this.root = newRoot; } this.record(); y.promote(); this.record(); x.demote(); this.record(); z.demote(); this.record(); } } insertRebalance(x) { let diffs = nodeDifferences(x.parent).sort(); while (x.parent && diffs.equals([0, 1])) { x.parent.promote(); x = x.parent; this.record(); diffs = nodeDifferences(x.parent).sort(); } if (!x.parent) { return; } let rotatingAroundRoot = x.parent.parent == null; let rankDifference = nodeDifference(x); if (rankDifference != 0) { return; } let xParent = x.parent; if (rankDifference == 0 && x.parent.left === x) { this.fix0Child( x, x.right, xParent, rotatingAroundRoot, rotateLeft, rotateRight ); } else if (rankDifference == 0 && x.parent.right === x) { this.fix0Child( x, x.left, xParent, rotatingAroundRoot, rotateRight, rotateLeft ); } } deleteRotate(x, y, leaning, rotatingAroundRoot, rotateLeft, rotateRight) { let newRoot = x; let factor = balanceFactor(y); switch (factor) { case 0: case leaning: newRoot = rotateLeft(x); break; default: rotateRight(y); newRoot = rotateLeft(x); break; } if (rotatingAroundRoot) { this.root = newRoot; } this.record(); [newRoot.left, newRoot.right, newRoot] .filter((x) => x) .forEach((n) => { updateRank(n); this.record(); }); return factor == 0; } deleteFixup(y, parent) { let x = y ? y : parent; let factor = balanceFactor(x); switch (factor) { case 0: updateRank(x); this.record(); return false; case -1: case 1: return true; } let rotatingAroundRoot = x.parent == null; let [z, leaning, toLeft, toRight] = [ x.left, -1, rotateRight, rotateLeft, ]; if (factor == 2) { [z, leaning, toLeft, toRight] = [ x.right, 1, rotateLeft, rotateRight, ]; } return this.deleteRotate( x, z, leaning, rotatingAroundRoot, toLeft, toRight ); } deleteRebalance(node, parent) { while (node || parent) { this.deleteFixup(node, parent); [node, parent] = [parent, parent ? parent.parent : null]; } } }