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; // FIXME: not enough, must be checked against differences // let bf = balanceFactor(node); // if (bf < -1 || bf > 1) { // return false; // } 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, rotateLeft, rotateRight) { let newRoot = x.parent; if (!y || nodeDifference(y) == 2) { newRoot = rotateRight(z); z.demote(); } else if (nodeDifference(y) == 1) { rotateLeft(x); newRoot = rotateRight(z); y.promote(); x.demote(); z.demote(); } return newRoot; } insertRebalance(x) { let diffs = nodeDifferences(x.parent).sort(); while (x.parent && diffs.equals([0, 1])) { x.parent.promote(); x = x.parent; diffs = nodeDifferences(x.parent).sort(); } if (!x.parent) { return; } let rotatingAroundRoot = x.parent.parent == null; let newRoot = x.parent; let rankDifference = nodeDifference(x); if (rankDifference != 0) { return; } let xParent = x.parent; if (rankDifference == 0 && x.parent.left === x) { newRoot = this.fix0Child( x, x.right, xParent, rotateLeft, rotateRight ); } else if (rankDifference == 0 && x.parent.right === x) { newRoot = this.fix0Child( x, x.left, xParent, rotateRight, rotateLeft ); } if (rotatingAroundRoot) { this.root = newRoot; } } 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; } [newRoot.left, newRoot.right, newRoot].filter(x => x).forEach(n => { updateRank(n) }); if (rotatingAroundRoot) { this.root = newRoot; } return factor == 0; } deleteFixup(y, parent) { let x = (y) ? y : parent; let factor = balanceFactor(x); switch (factor) { case 0: updateRank(x); 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]; } } }