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; } let differences = nodeDifferences(node).sort(); if ( (!differences.equals([1, 1]) && !differences.equals([1, 2])) || node.rank != 1 + Math.max(...differences) ) { return false; } recursive = recursive ?? true; 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("Fixing 0-child by single rotation: by z", z); z.demote(); this.record("Fixing 0-child by single rotation: demoting z", z); } else if (nodeDifference(y) == 1) { rotateLeft(x); this.record( "Fixing 0-child by double-rotation: first rotation by x", x ); newRoot = rotateRight(z); if (rotatingAroundRoot) { this.root = newRoot; } this.record( "Fixing 0-child by double-rotation: second rotation by z", z ); y.promote(); this.record("Fixing 0-child by double-rotation: promoting y", y); x.demote(); this.record("Fixing 0-child by double-rotation: demoting x", x); z.demote(); this.record("Fixing 0-child by double-rotation: demoting z", z); } } insertRebalance(x) { let diffs = nodeDifferences(x.parent).sort(); while (x.parent && diffs.equals([0, 1])) { x.parent.promote(); this.record( "Insertion rebalance: promoting (0, 1) parent", x.parent ); x = x.parent; 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); this.record("AVL deletion, fixing by double-rotation: by y", y); newRoot = rotateLeft(x); break; } if (rotatingAroundRoot) { this.root = newRoot; } this.record("AVL deletion, fixing by rotation by x", x); [newRoot.left, newRoot.right, newRoot] .filter((x) => x) .forEach((n) => { updateRank(n); this.record("Updating rank of nodes affected by rotations", n); }); return factor == 0; } deleteFixup(y, parent) { let x = y ? y : parent; let factor = balanceFactor(x); switch (factor) { case 0: updateRank(x); this.record( "Updating rank of node affected by the propagation of delete", 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]; } } }