UNPKG

deque-typed

Version:
1,260 lines (1,119 loc) 39.4 kB
/** * data-structure-typed * * @author Pablo Zeng * @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com> * @license MIT License */ import type { BinaryTreeDeleteResult, BTNRep, CRUD, EntryCallback, FamilyPosition, NodePredicate, OptNode, RBTNColor, RedBlackTreeOptions } from '../../types'; import { BST } from './bst'; import { IBinaryTree } from '../../interfaces'; export class RedBlackTreeNode<K = any, V = any> { key: K; value?: V; parent?: RedBlackTreeNode<K, V> = undefined; /** * Create a Red-Black Tree node. * @remarks Time O(1), Space O(1) * @param key - Node key. * @param [value] - Node value (unused in map mode trees). * @param color - Node color. */ constructor(key: K, value?: V, color: RBTNColor = 'BLACK') { this.key = key; this.value = value; this.color = color; } _left?: RedBlackTreeNode<K, V> | null | undefined = undefined; /** * Get the left child pointer. * @remarks Time O(1), Space O(1) * @returns Left child node, or null/undefined. */ get left(): RedBlackTreeNode<K, V> | null | undefined { return this._left; } /** * Set the left child and update its parent pointer. * @remarks Time O(1), Space O(1) * @param v - New left node, or null/undefined. * @returns void */ set left(v: RedBlackTreeNode<K, V> | null | undefined) { if (v) { v.parent = this; } this._left = v; } _right?: RedBlackTreeNode<K, V> | null | undefined = undefined; /** * Get the right child pointer. * @remarks Time O(1), Space O(1) * @returns Right child node, or null/undefined. */ get right(): RedBlackTreeNode<K, V> | null | undefined { return this._right; } /** * Set the right child and update its parent pointer. * @remarks Time O(1), Space O(1) * @param v - New right node, or null/undefined. * @returns void */ set right(v: RedBlackTreeNode<K, V> | null | undefined) { if (v) { v.parent = this; } this._right = v; } _height: number = 0; /** * Gets the height of the node (used in self-balancing trees). * @remarks Time O(1), Space O(1) * * @returns The height. */ get height(): number { return this._height; } /** * Sets the height of the node. * @remarks Time O(1), Space O(1) * * @param value - The new height. */ set height(value: number) { this._height = value; } _color: RBTNColor = 'BLACK'; /** * Gets the color of the node (used in Red-Black trees). * @remarks Time O(1), Space O(1) * * @returns The node's color. */ get color(): RBTNColor { return this._color; } /** * Sets the color of the node. * @remarks Time O(1), Space O(1) * * @param value - The new color. */ set color(value: RBTNColor) { this._color = value; } _count: number = 1; /** * Gets the count of nodes in the subtree rooted at this node (used in order-statistic trees). * @remarks Time O(1), Space O(1) * * @returns The subtree node count. */ get count(): number { return this._count; } /** * Sets the count of nodes in the subtree. * @remarks Time O(1), Space O(1) * * @param value - The new count. */ set count(value: number) { this._count = value; } /** * Gets the position of the node relative to its parent. * @remarks Time O(1), Space O(1) * * @returns The family position (e.g., 'ROOT', 'LEFT', 'RIGHT'). */ get familyPosition(): FamilyPosition { if (!this.parent) { return this.left || this.right ? 'ROOT' : 'ISOLATED'; } if (this.parent.left === this) { return this.left || this.right ? 'ROOT_LEFT' : 'LEFT'; } else if (this.parent.right === this) { return this.left || this.right ? 'ROOT_RIGHT' : 'RIGHT'; } return 'MAL_NODE'; } } /** * Represents a Red-Black Tree (self-balancing BST) supporting map-like mode and stable O(log n) updates. * @remarks Operation complexity depends on the method; see each method's docs. * @template K * @template V * @template R * 1. Efficient self-balancing, but not completely balanced. Compared with AVLTree, the addition and deletion efficiency is high, but the query efficiency is slightly lower. * 2. It is BST itself. Compared with Heap which is not completely ordered, RedBlackTree is completely ordered. * * @example * // basic Red-Black Tree with simple number keys * // Create a simple Red-Black Tree with numeric keys * const tree = new RedBlackTree([5, 2, 8, 1, 9]); * * tree.print(); * // _2___ * // / \ * // 1 _8_ * // / \ * // 5 9 * * // Verify the tree maintains sorted order * console.log([...tree.keys()]); // [1, 2, 5, 8, 9]; * * // Check size * console.log(tree.size); // 5; * @example * // Red-Black Tree with key-value pairs for lookups * interface Employee { * id: number; * name: string; * } * * // Create tree with employee data * const employees = new RedBlackTree<number, Employee>([ * [1, { id: 1, name: 'Alice' }], * [3, { id: 3, name: 'Charlie' }], * [2, { id: 2, name: 'Bob' }] * ]); * * // Retrieve employee by ID * const alice = employees.get(1); * console.log(alice?.name); // 'Alice'; * * // Verify sorted order by ID * console.log([...employees.keys()]); // [1, 2, 3]; * @example * // Red-Black Tree range search for filtering * interface Product { * name: string; * price: number; * } * * const products = new RedBlackTree<number, Product>([ * [10, { name: 'Item A', price: 10 }], * [25, { name: 'Item B', price: 25 }], * [40, { name: 'Item C', price: 40 }], * [50, { name: 'Item D', price: 50 }] * ]); * * // Find products in price range [20, 45] * const pricesInRange = products.rangeSearch([20, 45], node => { * return products.get(node)?.name; * }); * * console.log(pricesInRange); // ['Item B', 'Item C']; * @example * // Red-Black Tree as database index for stock market data * interface StockPrice { * symbol: string; * volume: number; * timestamp: Date; * } * * // Simulate real-time stock price index * const priceIndex = new RedBlackTree<number, StockPrice>([ * [142.5, { symbol: 'AAPL', volume: 1000000, timestamp: new Date() }], * [335.2, { symbol: 'MSFT', volume: 800000, timestamp: new Date() }], * [3285.04, { symbol: 'AMZN', volume: 500000, timestamp: new Date() }], * [267.98, { symbol: 'META', volume: 750000, timestamp: new Date() }], * [234.57, { symbol: 'GOOGL', volume: 900000, timestamp: new Date() }] * ]); * * // Find highest-priced stock * const maxPrice = priceIndex.getRightMost(); * console.log(priceIndex.get(maxPrice)?.symbol); // 'AMZN'; * * // Find stocks in price range [200, 400] for portfolio balancing * const stocksInRange = priceIndex.rangeSearch([200, 400], node => { * const stock = priceIndex.get(node); * return { * symbol: stock?.symbol, * price: node, * volume: stock?.volume * }; * }); * * console.log(stocksInRange.length); // 3; * console.log(stocksInRange.some((s: any) => s.symbol === 'GOOGL')); // true; * console.log(stocksInRange.some((s: any) => s.symbol === 'META')); // true; * console.log(stocksInRange.some((s: any) => s.symbol === 'MSFT')); // true; */ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implements IBinaryTree<K, V, R> { constructor( keysNodesEntriesOrRaws: Iterable< K | RedBlackTreeNode<K, V> | [K | null | undefined, V | undefined] | null | undefined | R > = [], options?: RedBlackTreeOptions<K, V, R> ) { super([], options); this._root = this.NIL; // Not part of the actual tree; used only as an internal cache hub. this._header = new RedBlackTreeNode<K, V>(undefined as K, undefined, 'BLACK'); this._header.parent = this.NIL; // Avoid using accessors here: they would set NIL.parent and can corrupt sentinel invariants. this._header._left = this.NIL; this._header._right = this.NIL; if (keysNodesEntriesOrRaws) { this.setMany(keysNodesEntriesOrRaws); } } protected override _root: RedBlackTreeNode<K, V> | undefined; /** * (Internal) Header sentinel: * - header.parent -> root * - header._left -> min (or NIL) * - header._right -> max (or NIL) * * IMPORTANT: * - This header is NOT part of the actual tree. * - Do NOT use `header.left` / `header.right` accessors for wiring: those setters update `NIL.parent` * and can corrupt sentinel invariants / cause hangs. Only touch `header._left/_right`. */ protected _header: RedBlackTreeNode<K, V>; /** * (Internal) Cache of the current minimum and maximum nodes. * Used for fast-path insert/update when keys are monotonic or near-boundary. */ protected _minNode: RedBlackTreeNode<K, V> | undefined; protected _maxNode: RedBlackTreeNode<K, V> | undefined; /** * Get the current root node. * @remarks Time O(1), Space O(1) * @returns Root node, or undefined. */ override get root(): RedBlackTreeNode<K, V> | undefined { return this._root; } /** * Create a red-black node for the given key/value (value ignored in map mode). * @remarks Time O(1), Space O(1) * @param key - See parameter type for details. * @param [value] - See parameter type for details. * @param color - See parameter type for details. * @returns A new RedBlackTreeNode instance. */ override createNode(key: K, value?: V, color: RBTNColor = 'BLACK'): RedBlackTreeNode<K, V> { return new RedBlackTreeNode<K, V>(key, value, color); } /** * Type guard: check whether the input is a RedBlackTreeNode. * @remarks Time O(1), Space O(1) * @param keyNodeOrEntry - See parameter type for details. * @returns True if the value is a RedBlackTreeNode. */ override isNode( keyNodeOrEntry: K | RedBlackTreeNode<K, V> | [K | null | undefined, V | undefined] | null | undefined ): keyNodeOrEntry is RedBlackTreeNode<K, V> { return keyNodeOrEntry instanceof RedBlackTreeNode; } /** * Remove all nodes and clear the key→value store (if in map mode). * @remarks Time O(n), Space O(1) * @returns void */ /** * Remove all nodes and clear internal caches. * @remarks Time O(n) average, Space O(1) */ override clear() { super.clear(); this._root = this.NIL; this._header.parent = this.NIL; this._setMinCache(undefined); this._setMaxCache(undefined); } /** * (Internal) Find a node by key using a tight BST walk (no allocations). * * NOTE: This uses `header.parent` as the canonical root pointer. * @remarks Time O(log n) average, Space O(1) */ protected _findNodeByKey(key: K): RedBlackTreeNode<K, V> | undefined { const NIL = this.NIL; const cmp = this._compare.bind(this); let cur = (this._header.parent) ?? NIL; while (cur !== NIL) { const c = cmp(key, cur.key); if (c < 0) cur = cur.left ?? NIL; else if (c > 0) cur = cur.right ?? NIL; else return cur; } return undefined; } /** * (Internal) In-order predecessor of a node in a BST. * @remarks Time O(log n) average, Space O(1) */ protected _predecessorOf(node: RedBlackTreeNode<K, V>): RedBlackTreeNode<K, V> | undefined { const NIL = this.NIL; if (node.left && node.left !== NIL) { let cur = node.left; while (cur.right && cur.right !== NIL) cur = cur.right; return cur; } let cur: RedBlackTreeNode<K, V> | undefined = node; let p = node.parent; while (p && cur === p.left) { cur = p; p = p.parent; } return p; } /** * (Internal) In-order successor of a node in a BST. * @remarks Time O(log n) average, Space O(1) */ protected _successorOf(node: RedBlackTreeNode<K, V>): RedBlackTreeNode<K, V> | undefined { const NIL = this.NIL; if (node.right && node.right !== NIL) { let cur = node.right; while (cur.left && cur.left !== NIL) cur = cur.left; return cur; } let cur: RedBlackTreeNode<K, V> | undefined = node; let p = node.parent; while (p && cur === p.right) { cur = p; p = p.parent; } return p; } /** * (Internal) Attach a new node directly under a known parent/side (no search). * * This is a performance-oriented helper used by boundary fast paths and hinted insertion. * It will: * - wire parent/child pointers (using accessors, so parent pointers are updated) * - initialize children to NIL * - mark the new node RED, then run insert fix-up * * Precondition: the chosen slot (parent.left/parent.right) is empty (NIL/null/undefined). * @remarks Time O(log n) average, Space O(1) */ protected _attachNewNode(parent: RedBlackTreeNode<K, V>, side: 'left' | 'right', node: RedBlackTreeNode<K, V>): void { const NIL = this.NIL; node.parent = parent; if (side === 'left') parent.left = node; else parent.right = node; node.left = NIL; node.right = NIL; node.color = 'RED'; this._insertFixup(node); if (this.isRealNode(this._root)) this._root.color = 'BLACK'; } /** * (Internal) a single source of truth for min/max is header._left/_right. * Keep legacy _minNode/_maxNode mirrored for compatibility. * @remarks Time O(1), Space O(1) */ /** * (Internal) Update min cache pointers (header._left is the canonical min pointer). * @remarks Time O(1), Space O(1) */ protected _setMinCache(node: RedBlackTreeNode<K, V> | undefined): void { this._minNode = node; this._header._left = node ?? this.NIL; } /** * (Internal) Update max cache pointers (header._right is the canonical max pointer). * @remarks Time O(1), Space O(1) */ protected _setMaxCache(node: RedBlackTreeNode<K, V> | undefined): void { this._maxNode = node; this._header._right = node ?? this.NIL; } /** * (Internal) Core set implementation returning the affected node. * * Hot path goals: * - Avoid double walks (search+insert): do a single traversal that either updates or inserts. * - Use header min/max caches to fast-path boundary inserts. * - Keep header._left/_right as canonical min/max pointers. * * Return value: * - `{ node, created:false }` when an existing key is updated * - `{ node, created:true }` when a new node is inserted * - `undefined` only on unexpected internal failure. * @remarks Time O(log n) average, Space O(1) */ protected _setKVNode(key: K, nextValue?: V): { node: RedBlackTreeNode<K, V>; created: boolean } | undefined { const NIL = this.NIL; const comparator = this._comparator; // Read via header to avoid undefined checks (header uses NIL when empty). const header = this._header; const minN = header._left ?? NIL; if (minN !== NIL) { const cMin = comparator(key, minN.key); if (cMin === 0) { minN.value = nextValue as V; if (this._isMapMode) this._store.set(key, minN); return { node: minN, created: false }; } // Boundary attach: if key is smaller than current min and min has no left child. // Inline NIL/null/undefined check to avoid isRealNode overhead on hot path. const minL = minN.left; if (cMin < 0 && (minL === NIL || minL === null || minL === undefined)) { const newNode = this.createNode(key, nextValue); this._attachNewNode(minN, 'left', newNode); if (this._isMapMode) this._store.set(newNode.key, newNode); this._size++; this._setMinCache(newNode); // If max is not initialized yet (tree had 0/1 nodes), mirror max too. if (header._right === NIL) this._setMaxCache(newNode); return { node: newNode, created: true }; } // Only touch max when key is not less than min. if (cMin > 0) { const maxN = header._right ?? NIL; // Boundary attach: if key is greater than current max and max has no right child. const cMax = comparator(key, maxN.key); if (cMax === 0) { maxN.value = nextValue as V; if (this._isMapMode) this._store.set(key, maxN); return { node: maxN, created: false }; } const maxR = maxN.right; if (cMax > 0 && (maxR === NIL || maxR === null || maxR === undefined)) { const newNode = this.createNode(key, nextValue); this._attachNewNode(maxN, 'right', newNode); if (this._isMapMode) this._store.set(newNode.key, newNode); this._size++; this._setMaxCache(newNode); if (header._left === NIL) this._setMinCache(newNode); return { node: newNode, created: true }; } } } // Normal path: single-pass search + insert/update (avoid double-walking the tree). const cmp = comparator; const isMapMode = this._isMapMode; const store = this._store; let current = this._header.parent ?? NIL; let parent: RedBlackTreeNode<K, V> | undefined; let lastCompared = 0; while (current !== NIL) { parent = current; lastCompared = cmp(key, current.key); if (lastCompared < 0) current = current.left ?? NIL; else if (lastCompared > 0) current = current.right ?? NIL; else { // Update existing. current.value = nextValue as V; if (isMapMode) store.set(key, current); return { node: current, created: false }; } } // Insert new. const newNode = this.createNode(key, nextValue); // createNode always returns a real node in RedBlackTree. newNode.parent = parent; if (!parent) { this._setRoot(newNode); } else if (lastCompared < 0) { parent.left = newNode; } else { parent.right = newNode; } newNode.left = NIL; newNode.right = NIL; newNode.color = 'RED'; this._insertFixup(newNode); if (this.isRealNode(this._root)) this._root.color = 'BLACK'; else return undefined; if (isMapMode) store.set(newNode.key, newNode); this._size++; // Maintain min/max caches on insertion (header.left/right are canonical). const hMin = this._header._left ?? NIL; const hMax = this._header._right ?? NIL; // Fast-path: empty tree or attaching directly to an extreme. if (hMin === NIL || hMax === NIL) { this._setMinCache(newNode); this._setMaxCache(newNode); } else if (parent === hMax && lastCompared > 0) { this._setMaxCache(newNode); } else if (parent === hMin && lastCompared < 0) { this._setMinCache(newNode); } else { if (cmp(newNode.key, hMin.key) < 0) this._setMinCache(newNode); if (cmp(newNode.key, hMax.key) > 0) this._setMaxCache(newNode); } return { node: newNode, created: true }; } /** * (Internal) Boolean wrapper around `_setKVNode`. * * Includes a map-mode update fast-path: * - If `isMapMode=true` and the key already exists in `_store`, then updating the value does not * require any tree search/rotation (tree shape depends only on key). * - This path is intentionally limited to `nextValue !== undefined` to preserve existing * semantics for `undefined` values. * @remarks Time O(log n) average, Space O(1) */ protected _setKV(key: K, nextValue?: V): boolean { if (this._isMapMode) { const store = this._store; const node = store.get(key); if (node) { node.value = nextValue as V; return true; } } return this._setKVNode(key, nextValue) !== undefined; } /** * Insert/update using a hint node to speed up nearby insertions. * * close to the expected insertion position (often the previously returned node in a loop). * * When the hint is a good fit (sorted / nearly-sorted insertion), this can avoid most of the * normal root-to-leaf search and reduce constant factors. * * When the hint does not match (random workloads), this will fall back to the normal set path. * @remarks Time O(log n) average, Space O(1) */ setWithHintNode(key: K, value: V, hint?: RedBlackTreeNode<K, V>): RedBlackTreeNode<K, V> | undefined { if (!hint || !this.isRealNode(hint)) { return this._setKVNode(key, value)?.node; } const cmp = this._compare.bind(this); const c0 = cmp(key, hint.key); if (c0 === 0) { hint.value = value; if (this._isMapMode) this._store.set(key, hint); return hint; } if (c0 < 0) { // Ultra-fast path: direct attach if the target slot is empty. if (!this.isRealNode(hint.left)) { const newNode = this.createNode(key, value); if (!this.isRealNode(newNode)) return undefined; this._attachNewNode(hint, 'left', newNode); if (this._isMapMode) this._store.set(key, newNode); this._size++; // Maintain header/min/max caches. const NIL = this.NIL; const hMin = this._header._left ?? NIL; if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode); const hMax = this._header._right ?? NIL; if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode); return newNode; } const pred = this._predecessorOf(hint); if (pred && cmp(pred.key, key) >= 0) { return this._setKVNode(key, value)?.node; } // Try attach as right of pred. if (pred && !this.isRealNode(pred.right)) { const newNode = this.createNode(key, value); if (!this.isRealNode(newNode)) return undefined; this._attachNewNode(pred, 'right', newNode); if (this._isMapMode) this._store.set(key, newNode); this._size++; // Maintain header/min/max caches. const NIL = this.NIL; const hMin = this._header._left ?? NIL; if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode); const hMax = this._header._right ?? NIL; if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode); return newNode; } return this._setKVNode(key, value)?.node; } // c0 > 0 // Ultra-fast path: direct attach if the target slot is empty. if (!this.isRealNode(hint.right)) { const newNode = this.createNode(key, value); if (!this.isRealNode(newNode)) return undefined; this._attachNewNode(hint, 'right', newNode); if (this._isMapMode) this._store.set(key, newNode); this._size++; // Maintain header/min/max caches. const NIL = this.NIL; const hMin = this._header._left ?? NIL; if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode); const hMax = this._header._right ?? NIL; if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode); return newNode; } const succ = this._successorOf(hint); if (succ && cmp(succ.key, key) <= 0) { return this._setKVNode(key, value)?.node; } if (succ && !this.isRealNode(succ.left)) { const newNode = this.createNode(key, value); if (!this.isRealNode(newNode)) return undefined; this._attachNewNode(succ, 'left', newNode); if (this._isMapMode) this._store.set(key, newNode); this._size++; // Maintain header/min/max caches. const NIL = this.NIL; const hMin = this._header._left ?? NIL; if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode); const hMax = this._header._right ?? NIL; if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode); return newNode; } return this._setKVNode(key, value)?.node; } /** * Boolean wrapper for setWithHintNode. * @remarks Time O(log n) average, Space O(1) */ setWithHint(key: K, value: V, hint?: RedBlackTreeNode<K, V>): boolean { return this.setWithHintNode(key, value, hint) !== undefined; } /** * Insert or update a key/value (map mode) or key-only (set mode). * * This method is optimized for: * - monotonic inserts via min/max boundary fast paths * - updates via a single-pass search (no double walk) * * @remarks Time O(log n) average, Space O(1) */ override set( keyNodeOrEntry: K | RedBlackTreeNode<K, V> | [K | null | undefined, V | undefined] | null | undefined, value?: V ): boolean { // Common path: tree.set(key, value) or tree.set([key, value]). if (!this.isNode(keyNodeOrEntry)) { if (keyNodeOrEntry === null || keyNodeOrEntry === undefined) return false; if (this.isEntry(keyNodeOrEntry)) { const key = keyNodeOrEntry[0]; if (key === null || key === undefined) return false; const nextValue = value ?? keyNodeOrEntry[1]; return this._setKV(key, nextValue); } // key-only return this._setKV(keyNodeOrEntry, value); } // Node insertion path (advanced usage) const [newNode, newValue] = this._keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value); if (!this.isRealNode(newNode)) return false; const insertStatus = this._insert(newNode); if (insertStatus === 'CREATED') { if (this.isRealNode(this._root)) { this._root.color = 'BLACK'; } else { return false; } if (this._isMapMode) { const n = this.getNode(newNode.key); if (this.isRealNode(n)) { n.value = newValue as V; this._store.set(n.key, n); } } this._size++; return true; } if (insertStatus === 'UPDATED') { if (this._isMapMode) { const n = this.getNode(newNode.key); if (this.isRealNode(n)) { n.value = newValue as V; this._store.set(n.key, n); } } return true; } return false; } /** * Delete a node by key/node/entry and rebalance as needed. * @remarks Time O(log n) average, Space O(1) * @param keyNodeEntryRawOrPredicate - Key, node, or [key, value] entry identifying the node to delete. * @returns Array with deletion metadata (removed node, rebalancing hint if any). */ override delete( keyNodeEntryRawOrPredicate: BTNRep<K, V, RedBlackTreeNode<K, V>> | NodePredicate<RedBlackTreeNode<K, V> | null> ): BinaryTreeDeleteResult<RedBlackTreeNode<K, V>>[] { if (keyNodeEntryRawOrPredicate === null) return []; const results: BinaryTreeDeleteResult<RedBlackTreeNode<K, V>>[] = []; let nodeToDelete: OptNode<RedBlackTreeNode<K, V>>; if (this._isPredicate(keyNodeEntryRawOrPredicate)) nodeToDelete = this.getNode(keyNodeEntryRawOrPredicate); else nodeToDelete = this.isRealNode(keyNodeEntryRawOrPredicate) ? keyNodeEntryRawOrPredicate : this.getNode(keyNodeEntryRawOrPredicate); if (!nodeToDelete) { return results; } // Track min/max cache updates before structural modifications. const willDeleteMin = nodeToDelete === this._minNode; const willDeleteMax = nodeToDelete === this._maxNode; const nextMin = willDeleteMin ? this._successorOf(nodeToDelete) : undefined; const nextMax = willDeleteMax ? this._predecessorOf(nodeToDelete) : undefined; let originalColor = nodeToDelete.color; let replacementNode: RedBlackTreeNode<K, V> | undefined; if (!this.isRealNode(nodeToDelete.left)) { if (nodeToDelete.right !== null) { replacementNode = nodeToDelete.right; this._transplant(nodeToDelete, nodeToDelete.right); } } else if (!this.isRealNode(nodeToDelete.right)) { replacementNode = nodeToDelete.left; this._transplant(nodeToDelete, nodeToDelete.left); } else { const successor = this.getLeftMost(node => node, nodeToDelete.right); if (successor) { originalColor = successor.color; if (successor.right !== null) replacementNode = successor.right; if (successor.parent === nodeToDelete) { if (this.isRealNode(replacementNode)) { replacementNode.parent = successor; } } else { if (successor.right !== null) { this._transplant(successor, successor.right); successor.right = nodeToDelete.right; } if (this.isRealNode(successor.right)) { successor.right.parent = successor; } } this._transplant(nodeToDelete, successor); successor.left = nodeToDelete.left; if (this.isRealNode(successor.left)) { successor.left.parent = successor; } successor.color = nodeToDelete.color; } } if (this._isMapMode) this._store.delete(nodeToDelete.key); this._size--; // Update min/max caches. if (this._size <= 0) { this._setMinCache(undefined); this._setMaxCache(undefined); } else { if (willDeleteMin) this._setMinCache(nextMin); if (willDeleteMax) this._setMaxCache(nextMax); // Fallback if successor/predecessor was unavailable. if (!this._minNode || !this.isRealNode(this._minNode)) { this._setMinCache(this.isRealNode(this._root) ? this.getLeftMost(n => n, this._root) : undefined); } if (!this._maxNode || !this.isRealNode(this._maxNode)) { this._setMaxCache(this.isRealNode(this._root) ? this.getRightMost(n => n, this._root) : undefined); } } if (originalColor === 'BLACK') { this._deleteFixup(replacementNode); } results.push({ deleted: nodeToDelete, needBalanced: undefined }); return results; } /** * Transform entries into a like-kind red-black tree with possibly different key/value types. * @remarks Time O(n) average, Space O(n) * @template MK * @template MV * @template MR * @param callback - Mapping function from (key, value, index, tree) to a new [key, value]. * @param [options] - See parameter type for details. * @param [thisArg] - See parameter type for details. * @returns A new RedBlackTree with mapped entries. */ override map<MK = K, MV = V, MR = any>( callback: EntryCallback<K, V | undefined, [MK, MV]>, options?: Partial<RedBlackTreeOptions<MK, MV, MR>>, thisArg?: unknown ): RedBlackTree<MK, MV, MR> { const out = this._createLike<MK, MV, MR>([], options); let index = 0; for (const [key, value] of this) { out.set(callback.call(thisArg, value, key, index++, this)); } return out; } /** * (Internal) Create an empty instance of the same concrete tree type. * @remarks Time O(1) average, Space O(1) */ protected override _createInstance<TK = K, TV = V, TR = R>(options?: Partial<RedBlackTreeOptions<TK, TV, TR>>): this { const Ctor = this.constructor as unknown as new ( iter?: Iterable<TK | RedBlackTreeNode<TK, TV> | [TK | null | undefined, TV | undefined] | null | undefined | TR>, opts?: RedBlackTreeOptions<TK, TV, TR> ) => RedBlackTree<TK, TV, TR>; return new Ctor([], { ...this._snapshotOptions<TK, TV, TR>(), ...(options ?? {}) }) as unknown as this; } /** * (Internal) Create a like-kind tree (same concrete class) populated from an iterable. * @remarks Time O(m log m) average (m = iterable length), Space O(m) */ protected override _createLike<TK = K, TV = V, TR = R>( iter: Iterable< TK | RedBlackTreeNode<TK, TV> | [TK | null | undefined, TV | undefined] | null | undefined | TR > = [], options?: Partial<RedBlackTreeOptions<TK, TV, TR>> ): RedBlackTree<TK, TV, TR> { const Ctor = this.constructor as unknown as new ( iter?: Iterable<TK | RedBlackTreeNode<TK, TV> | [TK | null | undefined, TV | undefined] | null | undefined | TR>, opts?: RedBlackTreeOptions<TK, TV, TR> ) => RedBlackTree<TK, TV, TR>; return new Ctor(iter, { ...this._snapshotOptions<TK, TV, TR>(), ...(options ?? {}) }); } /** * (Internal) Set the root pointer and keep header.parent in sync. * @remarks Time O(1), Space O(1) */ protected override _setRoot(v: RedBlackTreeNode<K, V> | undefined) { const NIL = this.NIL; if (v) { v.parent = undefined; } this._root = v; // Keep header root pointer in sync even for internal operations (rotations/transplants) // and for subclasses that may bypass _setKVNode. this._header.parent = v ?? NIL; } /** * (Internal) Replace a node in place while preserving its color. * @remarks Time O(1) average, Space O(1) */ protected override _replaceNode( oldNode: RedBlackTreeNode<K, V>, newNode: RedBlackTreeNode<K, V> ): RedBlackTreeNode<K, V> { newNode.color = oldNode.color; return super._replaceNode(oldNode, newNode); } /** * (Protected) Standard BST insert followed by red-black fix-up. * @remarks Time O(log n) average, Space O(1) * @param node - Node to insert. * @returns Status string: 'CREATED' or 'UPDATED'. */ protected _insert(node: RedBlackTreeNode<K, V>): CRUD { const NIL = this.NIL; const cmp = this._compare.bind(this); let current = this._header.parent ?? NIL; let parent: RedBlackTreeNode<K, V> | undefined; let lastCompared = 0; while (current !== NIL) { parent = current; lastCompared = cmp(node.key, current.key); if (lastCompared < 0) { current = current.left ?? NIL; } else if (lastCompared > 0) { current = current.right ?? NIL; } else { this._replaceNode(current, node); return 'UPDATED'; } } node.parent = parent; if (!parent) { this._setRoot(node); } else if (lastCompared < 0) { parent.left = node; } else { parent.right = node; } node.left = NIL; node.right = NIL; node.color = 'RED'; this._insertFixup(node); return 'CREATED'; } /** * (Protected) Transplant a subtree in place of another during deletion. * @remarks Time O(1), Space O(1) * @param u - Node to replace. * @param v - Replacement subtree root (may be undefined). * @returns void */ protected _transplant(u: RedBlackTreeNode<K, V>, v: RedBlackTreeNode<K, V> | undefined): void { if (!u.parent) { this._setRoot(v); } else if (u === u.parent.left) { u.parent.left = v; } else { u.parent.right = v; } if (v) { v.parent = u.parent; } } /** * (Protected) Restore red-black properties after insertion (recolor/rotate). * @remarks Time O(log n) average, Space O(1) * @param z - Recently inserted node. * @returns void */ protected _insertFixup(z: RedBlackTreeNode<K, V> | undefined): void { const leftRotate = this._leftRotate.bind(this); const rightRotate = this._rightRotate.bind(this); while (z) { const p = z.parent; if (!p || p.color !== 'RED') break; const gp = p.parent; if (!gp) break; if (p === gp.left) { const y = gp.right; if (y?.color === 'RED') { p.color = 'BLACK'; y.color = 'BLACK'; gp.color = 'RED'; z = gp; continue; } if (z === p.right) { z = p; leftRotate(z); } const p2 = z?.parent; const gp2 = p2?.parent; if (p2 && gp2) { p2.color = 'BLACK'; gp2.color = 'RED'; rightRotate(gp2); } } else { const y = gp.left; if (y?.color === 'RED') { p.color = 'BLACK'; y.color = 'BLACK'; gp.color = 'RED'; z = gp; continue; } if (z === p.left) { z = p; rightRotate(z); } const p2 = z?.parent; const gp2 = p2?.parent; if (p2 && gp2) { p2.color = 'BLACK'; gp2.color = 'RED'; leftRotate(gp2); } } break; } if (this.isRealNode(this._root)) this._root.color = 'BLACK'; } /** * (Protected) Restore red-black properties after deletion (recolor/rotate). * @remarks Time O(log n) average, Space O(1) * @param node - Child that replaced the deleted node (may be undefined). * @returns void */ protected _deleteFixup(node: RedBlackTreeNode<K, V> | undefined): void { if (!node || node === this.root || node.color === 'BLACK') { if (node) { node.color = 'BLACK'; } return; } while (node && node !== this.root && node.color === 'BLACK') { const parent: RedBlackTreeNode<K, V> | undefined = node.parent; if (!parent) { break; } if (node === parent.left) { let sibling = parent.right; if (sibling?.color === 'RED') { sibling.color = 'BLACK'; parent.color = 'RED'; this._leftRotate(parent); sibling = parent.right; } if ((sibling?.left?.color ?? 'BLACK') === 'BLACK') { if (sibling) sibling.color = 'RED'; node = parent; } else { if (sibling?.left) sibling.left.color = 'BLACK'; if (sibling) sibling.color = parent.color; parent.color = 'BLACK'; this._rightRotate(parent); node = this.root; } } else { let sibling = parent.left; if (sibling?.color === 'RED') { sibling.color = 'BLACK'; if (parent) parent.color = 'RED'; this._rightRotate(parent); if (parent) sibling = parent.left; } if ((sibling?.right?.color ?? 'BLACK') === 'BLACK') { if (sibling) sibling.color = 'RED'; node = parent; } else { if (sibling?.right) sibling.right.color = 'BLACK'; if (sibling) sibling.color = parent.color; if (parent) parent.color = 'BLACK'; this._leftRotate(parent); node = this.root; } } } if (node) { node.color = 'BLACK'; } } /** * (Protected) Perform a left rotation around x. * @remarks Time O(1), Space O(1) * @param x - Pivot node to rotate around. * @returns void */ protected _leftRotate(x: RedBlackTreeNode<K, V> | undefined): void { if (!x || !x.right) { return; } const y = x.right; x.right = y.left; if (y.left && y.left !== this.NIL) { y.left.parent = x; } y.parent = x.parent; if (!x.parent) { this._setRoot(y); } else if (x === x.parent.left) { x.parent.left = y; } else { x.parent.right = y; } y.left = x; x.parent = y; } /** * (Protected) Perform a right rotation around y. * @remarks Time O(1), Space O(1) * @param y - Pivot node to rotate around. * @returns void */ protected _rightRotate(y: RedBlackTreeNode<K, V> | undefined): void { if (!y || !y.left) { return; } const x = y.left; y.left = x.right; if (x.right && x.right !== this.NIL) { x.right.parent = y; } x.parent = y.parent; if (!y.parent) { this._setRoot(x); } else if (y === y.parent.left) { y.parent.left = x; } else { y.parent.right = x; } x.right = y; y.parent = x; } }