UNPKG

@foxglove/avl

Version:

Adelson-Velsky-Landis (AVL) self-balancing binary trees in TypeScript

590 lines 16.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AVLTree = void 0; const AVLNode_1 = require("./AVLNode"); function DEFAULT_COMPARE(a, b) { return a > b ? 1 : a < b ? -1 : 0; } /** * Implements a Adelson-Velsky-Landis (AVL) tree, a self-balancing binary tree. * Lookup, insertion, and deletion all take O(log n) time in both the average * and worst cases, where n is the number of nodes in the tree prior to the * operation. */ class AVLTree { constructor(comparator) { this._comparator = comparator ?? DEFAULT_COMPARE; this._size = 0; } /** * Number of nodes */ get size() { return this._size; } /** * Clear the tree * @return {AVLTree} */ clear() { this._root = undefined; this._size = 0; return this; } /** * Whether the tree contains a node with the given key */ has(key) { if (this._root) { let node = this._root; const comparator = this._comparator; while (node) { const cmp = comparator(key, node.key); if (cmp === 0) { return true; } else if (cmp < 0) { node = node.left; } else { node = node.right; } } } return false; } /** * Returns all keys in order */ *keys() { let current = this._root; const s = []; let done = false; while (!done) { if (current) { s.push(current); current = current.left; } else { if (s.length > 0) { current = s.pop(); yield current.key; current = current.right; } else { done = true; } } } } /** * Returns all values in order */ *values() { let current = this._root; const s = []; let done = false; while (!done) { if (current) { s.push(current); current = current.left; } else { if (s.length > 0) { current = s.pop(); yield current.value; current = current.right; } else { done = true; } } } } /** * Returns all key/value pairs in order */ *entries() { let current = this._root; const s = []; let done = false; while (!done) { if (current) { s.push(current); current = current.left; } else { if (s.length > 0) { current = s.pop(); yield [current.key, current.value]; current = current.right; } else { done = true; } } } } /** * Returns the entry with the minimum key */ minEntry() { let node = this._root; if (!node) { return undefined; } while (node.left) { node = node.left; } return [node.key, node.value]; } /** * Returns the entry with the maximum key */ maxEntry() { let node = this._root; if (!node) { return undefined; } while (node.right) { node = node.right; } return [node.key, node.value]; } /** * Minimum key */ minKey() { let node = this._root; if (!node) { return undefined; } while (node.left) { node = node.left; } return node.key; } /** * Maximum key */ maxKey() { let node = this._root; if (!node) { return undefined; } while (node.right) { node = node.right; } return node.key; } /** * Removes and returns the entry with smallest key */ shift() { let node = this._root; let returnValue; if (node) { while (node.left) { node = node.left; } returnValue = [node.key, node.value]; this.delete(node.key); } return returnValue; } /** * Removes and returns the entry with largest key */ pop() { let node = this._root; let returnValue; if (node) { while (node.right) { node = node.right; } returnValue = [node.key, node.value]; this.delete(node.key); } return returnValue; } /** * Search for an entry by key */ get(key) { const compare = this._comparator; let node = this._root; while (node) { const cmp = compare(key, node.key); if (cmp === 0) { return node.value; } else if (cmp < 0) { node = node.left; } else { node = node.right; } } return undefined; } /** * Execute a callback for each key/value entry in order */ forEach(callbackfn) { let current = this._root; const s = []; let done = false; while (!done) { // Reach the left most Node of the current Node if (current) { // Place pointer to a tree node on the stack // before traversing the node's left subtree s.push(current); current = current.left; } else { // BackTrack from the empty subtree and visit the Node // at the top of the stack; however, if the stack is // empty you are done if (s.length > 0) { current = s.pop(); callbackfn(current.value, current.key, this); // We have visited the node and its left // subtree. Now, it's right subtree's turn current = current.right; } else { done = true; } } } return this; } /** * Walk key range from `low` to `high` in order */ range(low, high, callbackfn) { const Q = []; const compare = this._comparator; let node = this._root; while (Q.length !== 0 || node) { if (node) { Q.push(node); if (compare(node.key, low) <= 0) { node = undefined; } else { node = node.left; } } else { node = Q.pop(); if (compare(node.key, high) > 0) { break; } else if (compare(node.key, low) >= 0) { callbackfn(node.value, node.key, this); } node = node.right; } } return this; } findLessThan(key) { let node = this.findGreaterThanOrEqualNode(key); if (!node) { // Check if there is any key less than `key` node = this._root; if (!node) { return undefined; } while (node.right) { node = node.right; } return this._comparator(node.key, key) < 0 ? [node.key, node.value] : undefined; } // Return the node just before the first node with key greater than or equal to `key`, if any const lt = (0, AVLNode_1.prevNode)(node); return lt ? [lt.key, lt.value] : undefined; } findLessThanOrEqual(key) { let node = this.findGreaterThanOrEqualNode(key); if (!node) { // Check if there is any key less than `key` node = this._root; if (!node) { return undefined; } while (node.right) { node = node.right; } return this._comparator(node.key, key) < 0 ? [node.key, node.value] : undefined; } // Check if the found node is an exact match if (this._comparator(node.key, key) === 0) { return [node.key, node.value]; } // Return the node just before the first node with key greater than or equal to `key`, if any const lt = (0, AVLNode_1.prevNode)(node); return lt ? [lt.key, lt.value] : undefined; } findGreaterThan(key) { const Q = []; const compare = this._comparator; let node = this._root; while (Q.length !== 0 || node) { if (node) { Q.push(node); if (compare(node.key, key) <= 0) { node = undefined; } else { node = node.left; } } else { node = Q.pop(); if (compare(node.key, key) > 0) { return [node.key, node.value]; } node = node.right; } } return undefined; } findGreaterThanOrEqual(key) { const node = this.findGreaterThanOrEqualNode(key); return node ? [node.key, node.value] : undefined; } findGreaterThanOrEqualNode(key) { const Q = []; const compare = this._comparator; let node = this._root; while (Q.length !== 0 || node) { if (node) { Q.push(node); if (compare(node.key, key) <= 0) { node = undefined; } else { node = node.left; } } else { node = Q.pop(); if (compare(node.key, key) >= 0) { return node; } node = node.right; } } return undefined; } /** * Insert a new key/value pair into the tree or update an existing entry */ set(key, value) { if (!this._root) { this._root = { parent: undefined, left: undefined, right: undefined, balanceFactor: 0, key, value, }; this._size++; return this; } const compare = this._comparator; let node = this._root; let parent; let cmp = 0; while (node) { cmp = compare(key, node.key); parent = node; if (cmp === 0) { node.value = value; return this; } else if (cmp < 0) { node = node.left; } else { node = node.right; } } if (parent == undefined) { throw new Error(`failed to find parent node for insert`); } const newNode = { left: undefined, right: undefined, balanceFactor: 0, parent, key, value, }; if (cmp <= 0) { parent.left = newNode; } else { parent.right = newNode; } let newRoot; while (parent) { cmp = compare(parent.key, key); if (cmp < 0) { parent.balanceFactor -= 1; } else { parent.balanceFactor += 1; } if (parent.balanceFactor === 0) { break; } else if (parent.balanceFactor < -1) { if (parent.right?.balanceFactor === 1) { (0, AVLNode_1.rotateRight)(parent.right); } newRoot = (0, AVLNode_1.rotateLeft)(parent); if (parent === this._root) { this._root = newRoot; } break; } else if (parent.balanceFactor > 1) { if (parent.left?.balanceFactor === -1) { (0, AVLNode_1.rotateLeft)(parent.left); } newRoot = (0, AVLNode_1.rotateRight)(parent); if (parent === this._root) { this._root = newRoot; } break; } parent = parent.parent; } this._size++; return this; } /** * Finds the first matching node by key and removes it */ delete(key) { if (!this._root) { return false; } let node = this._root; const compare = this._comparator; let cmp = 0; while (node) { cmp = compare(key, node.key); if (cmp === 0) { break; } else if (cmp < 0) { node = node.left; } else { node = node.right; } } if (!node) { return false; } let max, min; if (node.left) { max = node.left; while (max.left || max.right) { while (max.right) { max = max.right; } node.key = max.key; node.value = max.value; if (max.left) { node = max; max = max.left; } } node.key = max.key; node.value = max.value; node = max; } if (node.right) { min = node.right; while (min.left || min.right) { while (min.left) { min = min.left; } node.key = min.key; node.value = min.value; if (min.right) { node = min; min = min.right; } } node.key = min.key; node.value = min.value; node = min; } let parent = node.parent; let pp = node; let newRoot; while (parent) { if (parent.left === pp) { parent.balanceFactor -= 1; } else { parent.balanceFactor += 1; } if (parent.balanceFactor < -1) { if (parent.right?.balanceFactor === 1) { (0, AVLNode_1.rotateRight)(parent.right); } newRoot = (0, AVLNode_1.rotateLeft)(parent); if (parent === this._root) { this._root = newRoot; } parent = newRoot; } else if (parent.balanceFactor > 1) { if (parent.left?.balanceFactor === -1) { (0, AVLNode_1.rotateLeft)(parent.left); } newRoot = (0, AVLNode_1.rotateRight)(parent); if (parent === this._root) { this._root = newRoot; } parent = newRoot; } const parentBalanceFactor = parent?.balanceFactor; if (parentBalanceFactor === -1 || parentBalanceFactor === 1) { break; } pp = parent; parent = parent?.parent; } if (node.parent) { if (node.parent.left === node) { node.parent.left = undefined; } else { node.parent.right = undefined; } } if (node === this._root) { this._root = undefined; } this._size--; return true; } /** * Returns a string representation of the tree - primitive horizontal print-out */ toString(printEntry) { return (0, AVLNode_1.printNodes)(this._root, printEntry).trimEnd(); } } exports.AVLTree = AVLTree; //# sourceMappingURL=AVLTree.js.map