@foxglove/avl
Version:
Adelson-Velsky-Landis (AVL) self-balancing binary trees in TypeScript
590 lines • 16.6 kB
JavaScript
"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