tree-multimap-typed
Version:
612 lines (611 loc) • 23.1 kB
JavaScript
"use strict";
/**
* data-structure-typed
*
* @author Pablo Zeng
* @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com>
* @license MIT License
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.AVLTree = exports.AVLTreeNode = void 0;
const bst_1 = require("./bst");
/**
* Represents a Node in an AVL (Adelson-Velsky and Landis) Tree.
* It extends a BSTNode and ensures the 'height' property is maintained.
*
* @template K - The type of the key.
* @template V - The type of the value.
*/
class AVLTreeNode extends bst_1.BSTNode {
/**
* Creates an instance of AVLTreeNode.
* @remarks Time O(1), Space O(1)
*
* @param key - The key of the node.
* @param [value] - The value associated with the key.
*/
constructor(key, value) {
super(key, value);
this.parent = undefined;
this._left = undefined;
this._right = undefined;
}
/**
* Gets the left child of the node.
* @remarks Time O(1), Space O(1)
*
* @returns The left child.
*/
get left() {
return this._left;
}
/**
* Sets the left child of the node and updates its parent reference.
* @remarks Time O(1), Space O(1)
*
* @param v - The node to set as the left child.
*/
set left(v) {
if (v) {
v.parent = this;
}
this._left = v;
}
/**
* Gets the right child of the node.
* @remarks Time O(1), Space O(1)
*
* @returns The right child.
*/
get right() {
return this._right;
}
/**
* Sets the right child of the node and updates its parent reference.
* @remarks Time O(1), Space O(1)
*
* @param v - The node to set as the right child.
*/
set right(v) {
if (v) {
v.parent = this;
}
this._right = v;
}
}
exports.AVLTreeNode = AVLTreeNode;
/**
* Represents a self-balancing AVL (Adelson-Velsky and Landis) Tree.
* This tree extends BST and performs rotations on add/delete to maintain balance.
*
* @template K - The type of the key.
* @template V - The type of the value.
* @template R - The type of the raw data object (if using `toEntryFn`).
*
* 1. Height-Balanced: Each node's left and right subtrees differ in height by no more than one.
* 2. Automatic Rebalancing: AVL trees rebalance themselves automatically during insertions and deletions.
* 3. Rotations for Balancing: Utilizes rotations (single or double) to maintain balance after updates.
* 4. Order Preservation: Maintains the binary search tree property where left child values are less than the parent, and right child values are greater.
* 5. Efficient Lookups: Offers O(log n) search time, where 'n' is the number of nodes, due to its balanced nature.
* 6. Complex Insertions and Deletions: Due to rebalancing, these operations are more complex than in a regular BST.
* 7. Path Length: The path length from the root to any leaf is longer compared to an unbalanced BST, but shorter than a linear chain of nodes.@example
* // Find elements in a range
* // In interval queries, AVL trees, with their strictly balanced structure and lower height, offer better query efficiency, making them ideal for frequent and high-performance interval queries. In contrast, Red-Black trees, with lower update costs, are more suitable for scenarios involving frequent insertions and deletions where the requirements for interval queries are less demanding.
* type Datum = { timestamp: Date; temperature: number };
* // Fixed dataset of CPU temperature readings
* const cpuData: Datum[] = [
* { timestamp: new Date('2024-12-02T00:00:00'), temperature: 55.1 },
* { timestamp: new Date('2024-12-02T00:01:00'), temperature: 56.3 },
* { timestamp: new Date('2024-12-02T00:02:00'), temperature: 54.8 },
* { timestamp: new Date('2024-12-02T00:03:00'), temperature: 57.2 },
* { timestamp: new Date('2024-12-02T00:04:00'), temperature: 58.0 },
* { timestamp: new Date('2024-12-02T00:05:00'), temperature: 59.4 },
* { timestamp: new Date('2024-12-02T00:06:00'), temperature: 60.1 },
* { timestamp: new Date('2024-12-02T00:07:00'), temperature: 61.3 },
* { timestamp: new Date('2024-12-02T00:08:00'), temperature: 62.0 },
* { timestamp: new Date('2024-12-02T00:09:00'), temperature: 63.5 },
* { timestamp: new Date('2024-12-02T00:10:00'), temperature: 64.0 },
* { timestamp: new Date('2024-12-02T00:11:00'), temperature: 62.8 },
* { timestamp: new Date('2024-12-02T00:12:00'), temperature: 61.5 },
* { timestamp: new Date('2024-12-02T00:13:00'), temperature: 60.2 },
* { timestamp: new Date('2024-12-02T00:14:00'), temperature: 59.8 },
* { timestamp: new Date('2024-12-02T00:15:00'), temperature: 58.6 },
* { timestamp: new Date('2024-12-02T00:16:00'), temperature: 57.4 },
* { timestamp: new Date('2024-12-02T00:17:00'), temperature: 56.2 },
* { timestamp: new Date('2024-12-02T00:18:00'), temperature: 55.7 },
* { timestamp: new Date('2024-12-02T00:19:00'), temperature: 54.5 },
* { timestamp: new Date('2024-12-02T00:20:00'), temperature: 53.2 },
* { timestamp: new Date('2024-12-02T00:21:00'), temperature: 52.8 },
* { timestamp: new Date('2024-12-02T00:22:00'), temperature: 51.9 },
* { timestamp: new Date('2024-12-02T00:23:00'), temperature: 50.5 },
* { timestamp: new Date('2024-12-02T00:24:00'), temperature: 49.8 },
* { timestamp: new Date('2024-12-02T00:25:00'), temperature: 48.7 },
* { timestamp: new Date('2024-12-02T00:26:00'), temperature: 47.5 },
* { timestamp: new Date('2024-12-02T00:27:00'), temperature: 46.3 },
* { timestamp: new Date('2024-12-02T00:28:00'), temperature: 45.9 },
* { timestamp: new Date('2024-12-02T00:29:00'), temperature: 45.0 }
* ];
*
* // Create an AVL tree to store CPU temperature data
* const cpuTemperatureTree = new AVLTree<Date, number, Datum>(cpuData, {
* toEntryFn: ({ timestamp, temperature }) => [timestamp, temperature]
* });
*
* // Query a specific time range (e.g., from 00:05 to 00:15)
* const rangeStart = new Date('2024-12-02T00:05:00');
* const rangeEnd = new Date('2024-12-02T00:15:00');
* const rangeResults = cpuTemperatureTree.rangeSearch([rangeStart, rangeEnd], node => ({
* minute: node ? node.key.getMinutes() : 0,
* temperature: cpuTemperatureTree.get(node ? node.key : undefined)
* }));
*
* console.log(rangeResults); // [
* // { minute: 5, temperature: 59.4 },
* // { minute: 6, temperature: 60.1 },
* // { minute: 7, temperature: 61.3 },
* // { minute: 8, temperature: 62 },
* // { minute: 9, temperature: 63.5 },
* // { minute: 10, temperature: 64 },
* // { minute: 11, temperature: 62.8 },
* // { minute: 12, temperature: 61.5 },
* // { minute: 13, temperature: 60.2 },
* // { minute: 14, temperature: 59.8 },
* // { minute: 15, temperature: 58.6 }
* // ]
*/
class AVLTree extends bst_1.BST {
/**
* Creates an instance of AVLTree.
* @remarks Time O(N log N) (from `addMany` with balanced add). Space O(N).
*
* @param [keysNodesEntriesOrRaws=[]] - An iterable of items to add.
* @param [options] - Configuration options for the AVL tree.
*/
constructor(keysNodesEntriesOrRaws = [], options) {
super([], options);
// Note: super.addMany is called, which in BST defaults to balanced add.
if (keysNodesEntriesOrRaws)
super.addMany(keysNodesEntriesOrRaws);
}
/**
* (Protected) Creates a new AVL tree node.
* @remarks Time O(1), Space O(1)
*
* @param key - The key for the new node.
* @param [value] - The value for the new node.
* @returns The newly created AVLTreeNode.
*/
_createNode(key, value) {
return new AVLTreeNode(key, this._isMapMode ? undefined : value);
}
/**
* Checks if the given item is an `AVLTreeNode` instance.
* @remarks Time O(1), Space O(1)
*
* @param keyNodeOrEntry - The item to check.
* @returns True if it's an AVLTreeNode, false otherwise.
*/
isNode(keyNodeOrEntry) {
return keyNodeOrEntry instanceof AVLTreeNode;
}
/**
* Adds a new node to the AVL tree and balances the tree path.
* @remarks Time O(log N) (O(H) for BST add + O(H) for `_balancePath`). Space O(H) for path/recursion.
*
* @param keyNodeOrEntry - The key, node, or entry to add.
* @param [value] - The value, if providing just a key.
* @returns True if the addition was successful, false otherwise.
*/
add(keyNodeOrEntry, value) {
if (keyNodeOrEntry === null)
return false;
const inserted = super.add(keyNodeOrEntry, value);
// If insertion was successful, balance the path from the new node up to the root.
if (inserted)
this._balancePath(keyNodeOrEntry);
return inserted;
}
/**
* Deletes a node from the AVL tree and re-balances the tree.
* @remarks Time O(log N) (O(H) for BST delete + O(H) for `_balancePath`). Space O(H) for path/recursion.
*
* @param keyNodeOrEntry - The node to delete.
* @returns An array containing deletion results.
*/
delete(keyNodeOrEntry) {
const deletedResults = super.delete(keyNodeOrEntry);
// After deletion, balance the path from the parent of the *physically deleted* node.
for (const { needBalanced } of deletedResults) {
if (needBalanced) {
this._balancePath(needBalanced);
}
}
return deletedResults;
}
/**
* Rebuilds the tree to be perfectly balanced.
* @remarks AVL trees are already height-balanced, but this makes them *perfectly* balanced (minimal height and all leaves at N or N-1).
* Time O(N) (O(N) for DFS, O(N) for sorted build). Space O(N) for node array and recursion stack.
*
* @param [iterationType=this.iterationType] - The traversal method for the initial node export.
* @returns True if successful, false if the tree was empty.
*/
perfectlyBalance(iterationType = this.iterationType) {
const nodes = this.dfs(node => node, 'IN', false, this._root, iterationType);
const n = nodes.length;
if (n === 0)
return false;
this._clearNodes();
// Build balanced tree from sorted array
const build = (l, r, parent) => {
if (l > r)
return undefined;
const m = l + ((r - l) >> 1);
const root = nodes[m];
root.left = build(l, m - 1, root);
root.right = build(m + 1, r, root);
root.parent = parent;
// Update height during the build
const lh = root.left ? root.left.height : -1;
const rh = root.right ? root.right.height : -1;
root.height = Math.max(lh, rh) + 1;
return root;
};
const newRoot = build(0, n - 1, undefined);
this._setRoot(newRoot);
this._size = n;
return true;
}
/**
* Creates a new AVLTree by mapping each [key, value] pair.
* @remarks Time O(N log N) (O(N) iteration + O(log M) `add` for each item into the new tree). Space O(N) for the new tree.
*
* @template MK - New key type.
* @template MV - New value type.
* @template MR - New raw type.
* @param callback - A function to map each [key, value] pair.
* @param [options] - Options for the new AVLTree.
* @param [thisArg] - `this` context for the callback.
* @returns A new, mapped AVLTree.
*/
map(callback, options, thisArg) {
const out = this._createLike([], options);
let index = 0;
// Iterates in-order
for (const [key, value] of this) {
// `add` on the new tree will be O(log N) and will self-balance.
out.add(callback.call(thisArg, key, value, index++, this));
}
return out;
}
/**
* (Protected) Creates a new, empty instance of the same AVLTree constructor.
* @remarks Time O(1)
*
* @template TK, TV, TR - Generic types for the new instance.
* @param [options] - Options for the new tree.
* @returns A new, empty tree.
*/
_createInstance(options) {
const Ctor = this.constructor;
return new Ctor([], Object.assign(Object.assign({}, this._snapshotOptions()), (options !== null && options !== void 0 ? options : {})));
}
/**
* (Protected) Creates a new instance of the same AVLTree constructor, potentially with different generic types.
* @remarks Time O(N log N) (from constructor) due to processing the iterable.
*
* @template TK, TV, TR - Generic types for the new instance.
* @param [iter=[]] - An iterable to populate the new tree.
* @param [options] - Options for the new tree.
* @returns A new AVLTree.
*/
_createLike(iter = [], options) {
const Ctor = this.constructor;
return new Ctor(iter, Object.assign(Object.assign({}, this._snapshotOptions()), (options !== null && options !== void 0 ? options : {})));
}
/**
* (Protected) Swaps properties of two nodes, including height.
* @remarks Time O(H) (due to `ensureNode`), but O(1) if nodes are passed directly.
*
* @param srcNode - The source node.
* @param destNode - The destination node.
* @returns The `destNode` (now holding `srcNode`'s properties).
*/
_swapProperties(srcNode, destNode) {
const srcNodeEnsured = this.ensureNode(srcNode);
const destNodeEnsured = this.ensureNode(destNode);
if (srcNodeEnsured && destNodeEnsured) {
const { key, value, height } = destNodeEnsured;
const tempNode = this._createNode(key, value);
if (tempNode) {
tempNode.height = height;
// Copy src to dest
destNodeEnsured.key = srcNodeEnsured.key;
if (!this._isMapMode)
destNodeEnsured.value = srcNodeEnsured.value;
destNodeEnsured.height = srcNodeEnsured.height;
// Copy temp (original dest) to src
srcNodeEnsured.key = tempNode.key;
if (!this._isMapMode)
srcNodeEnsured.value = tempNode.value;
srcNodeEnsured.height = tempNode.height;
}
return destNodeEnsured;
}
return undefined;
}
/**
* (Protected) Calculates the balance factor (height(right) - height(left)).
* @remarks Time O(1) (assumes heights are stored).
*
* @param node - The node to check.
* @returns The balance factor (positive if right-heavy, negative if left-heavy).
*/
_balanceFactor(node) {
const left = node.left ? node.left.height : -1;
const right = node.right ? node.right.height : -1;
return right - left;
}
/**
* (Protected) Recalculates and updates the height of a node based on its children's heights.
* @remarks Time O(1) (assumes children's heights are correct).
*
* @param node - The node to update.
*/
_updateHeight(node) {
const leftHeight = node.left ? node.left.height : -1;
const rightHeight = node.right ? node.right.height : -1;
node.height = 1 + Math.max(leftHeight, rightHeight);
}
/**
* (Protected) Performs a Left-Left (LL) rotation (a single right rotation).
* @remarks Time O(1), Space O(1)
*
* @param A - The unbalanced node (root of the unbalanced subtree).
*/
_balanceLL(A) {
const parentOfA = A.parent;
const B = A.left; // The left child
if (B !== null)
A.parent = B;
if (B && B.right) {
B.right.parent = A;
}
if (B)
B.parent = parentOfA;
// Update parent's child pointer
if (A === this.root) {
if (B)
this._setRoot(B);
}
else {
if ((parentOfA === null || parentOfA === void 0 ? void 0 : parentOfA.left) === A) {
parentOfA.left = B;
}
else {
if (parentOfA)
parentOfA.right = B;
}
}
// Perform rotation
if (B) {
A.left = B.right;
B.right = A;
}
this._updateHeight(A);
if (B)
this._updateHeight(B);
}
/**
* (Protected) Performs a Left-Right (LR) double rotation.
* @remarks Time O(1), Space O(1)
*
* @param A - The unbalanced node (root of the unbalanced subtree).
*/
_balanceLR(A) {
const parentOfA = A.parent;
const B = A.left;
let C = undefined;
if (B) {
C = B.right; // The "middle" node
}
if (A && C !== null)
A.parent = C;
if (B && C !== null)
B.parent = C;
if (C) {
if (C.left) {
if (B !== null)
C.left.parent = B;
}
if (C.right) {
C.right.parent = A;
}
C.parent = parentOfA;
}
// Update parent's child pointer
if (A === this.root) {
if (C)
this._setRoot(C);
}
else {
if (parentOfA) {
if (parentOfA.left === A) {
parentOfA.left = C;
}
else {
parentOfA.right = C;
}
}
}
// Perform rotation
if (C) {
A.left = C.right;
if (B)
B.right = C.left;
C.left = B;
C.right = A;
}
this._updateHeight(A);
if (B)
this._updateHeight(B);
if (C)
this._updateHeight(C);
}
/**
* (Protected) Performs a Right-Right (RR) rotation (a single left rotation).
* @remarks Time O(1), Space O(1)
*
* @param A - The unbalanced node (root of the unbalanced subtree).
*/
_balanceRR(A) {
const parentOfA = A.parent;
const B = A.right; // The right child
if (B !== null)
A.parent = B;
if (B) {
if (B.left) {
B.left.parent = A;
}
B.parent = parentOfA;
}
// Update parent's child pointer
if (A === this.root) {
if (B)
this._setRoot(B);
}
else {
if (parentOfA) {
if (parentOfA.left === A) {
parentOfA.left = B;
}
else {
parentOfA.right = B;
}
}
}
// Perform rotation
if (B) {
A.right = B.left;
B.left = A;
}
this._updateHeight(A);
if (B)
this._updateHeight(B);
}
/**
* (Protected) Performs a Right-Left (RL) double rotation.
* @remarks Time O(1), Space O(1)
*
* @param A - The unbalanced node (root of the unbalanced subtree).
*/
_balanceRL(A) {
const parentOfA = A.parent;
const B = A.right;
let C = undefined;
if (B) {
C = B.left; // The "middle" node
}
if (C !== null)
A.parent = C;
if (B && C !== null)
B.parent = C;
if (C) {
if (C.left) {
C.left.parent = A;
}
if (C.right) {
if (B !== null)
C.right.parent = B;
}
C.parent = parentOfA;
}
// Update parent's child pointer
if (A === this.root) {
if (C)
this._setRoot(C);
}
else {
if (parentOfA) {
if (parentOfA.left === A) {
parentOfA.left = C;
}
else {
parentOfA.right = C;
}
}
}
// Perform rotation
if (C)
A.right = C.left;
if (B && C)
B.left = C.right;
if (C)
C.left = A;
if (C)
C.right = B;
this._updateHeight(A);
if (B)
this._updateHeight(B);
if (C)
this._updateHeight(C);
}
/**
* (Protected) Traverses up the tree from the specified node, updating heights and performing rotations as needed.
* @remarks Time O(log N) (O(H)), as it traverses the path to root. Space O(H) for the path array.
*
* @param node - The node to start balancing from (e.g., the newly inserted node or parent of the deleted node).
*/
_balancePath(node) {
// Get the path from the node to the root.
node = this.ensureNode(node);
const path = this.getPathToRoot(node, node => node, false);
// Iterate up the path (from node to root)
for (let i = 0; i < path.length; i++) {
const A = path[i];
if (A) {
this._updateHeight(A);
// Check the balance factor
switch (this._balanceFactor(A)) {
case -2: // Left-heavy
if (A && A.left) {
if (this._balanceFactor(A.left) <= 0) {
// Left-Left case
this._balanceLL(A);
}
else {
// Left-Right case
this._balanceLR(A);
}
}
break;
case +2: // Right-heavy
if (A && A.right) {
if (this._balanceFactor(A.right) >= 0) {
// Right-Right case
this._balanceRR(A);
}
else {
// Right-Left case
this._balanceRL(A);
}
}
}
}
}
}
/**
* (Protected) Replaces a node, ensuring height is copied.
* @remarks Time O(1)
*
* @param oldNode - The node to be replaced.
* @param newNode - The node to insert.
* @returns The `newNode`.
*/
_replaceNode(oldNode, newNode) {
// When replacing a node (e.g., on duplicate key), preserve the height.
newNode.height = oldNode.height;
return super._replaceNode(oldNode, newNode);
}
}
exports.AVLTree = AVLTree;