deque-typed
Version:
841 lines (776 loc) • 28.2 kB
text/typescript
/**
* data-structure-typed
*
* @author Pablo Zeng
* @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com>
* @license MIT License
*/
import { BST } from './bst';
import type {
AVLTreeOptions,
BinaryTreeDeleteResult,
BinaryTreeOptions,
BSTNOptKeyOrNode,
EntryCallback,
FamilyPosition,
IterationType,
RBTNColor
} from '../../types';
import { IBinaryTree } from '../../interfaces';
/**
* 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.
*/
export class AVLTreeNode<K = any, V = any> {
key: K;
value?: V;
parent?: AVLTreeNode<K, V> = undefined;
/**
* 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: K, value?: V) {
this.key = key;
this.value = value;
}
_left?: AVLTreeNode<K, V> | null | undefined = undefined;
/**
* Gets the left child of the node.
* @remarks Time O(1), Space O(1)
*
* @returns The left child.
*/
get left(): AVLTreeNode<K, V> | null | undefined {
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: AVLTreeNode<K, V> | null | undefined) {
if (v) {
v.parent = this;
}
this._left = v;
}
_right?: AVLTreeNode<K, V> | null | undefined = undefined;
/**
* Gets the right child of the node.
* @remarks Time O(1), Space O(1)
*
* @returns The right child.
*/
get right(): AVLTreeNode<K, V> | null | undefined {
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: AVLTreeNode<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 self-balancing AVL (Adelson-Velsky and Landis) Tree.
* This tree extends BST and performs rotations on set/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
* // basic AVLTree creation and add operation
* // Create a simple AVLTree with initial values
* const tree = new AVLTree([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;
*
* // Add a new element
* tree.set(3);
* console.log(tree.size); // 6;
* console.log([...tree.keys()]); // [1, 2, 3, 5, 8, 9];
* @example
* // AVLTree has and get operations
* const tree = new AVLTree<number>([11, 3, 15, 1, 8, 13, 16, 2, 6, 9, 12, 14, 4, 7, 10, 5]);
*
* // Check if element exists
* console.log(tree.has(6)); // true;
* console.log(tree.has(99)); // false;
*
* // Get node by key
* const node = tree.getNode(6);
* console.log(node?.key); // 6;
*
* // Verify tree is balanced
* console.log(tree.isAVLBalanced()); // true;
* @example
* // AVLTree delete and balance verification
* const tree = new AVLTree([11, 3, 15, 1, 8, 13, 16, 2, 6, 9, 12, 14, 4, 7, 10, 5]);
*
* // Delete an element
* tree.delete(10);
* console.log(tree.has(10)); // false;
*
* // Tree should remain balanced after deletion
* console.log(tree.isAVLBalanced()); // true;
*
* // Size decreased
* console.log(tree.size); // 15;
*
* // Remaining elements are still sorted
* const keys = [...tree.keys()];
* console.log(keys); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16];
* @example
* // AVLTree for university ranking system with strict balance
* interface University {
* name: string;
* rank: number;
* students: number;
* }
*
* // AVLTree provides highest search efficiency with strict balance
* // (every node's left/right subtrees differ by at most 1 in height)
* const universityTree = new AVLTree<number, University>([
* [1, { name: 'MIT', rank: 1, students: 1200 }],
* [5, { name: 'Stanford', rank: 5, students: 1800 }],
* [3, { name: 'Harvard', rank: 3, students: 2300 }],
* [2, { name: 'Caltech', rank: 2, students: 400 }],
* [4, { name: 'CMU', rank: 4, students: 1500 }]
* ]);
*
* // Quick lookup by rank
* const mit = universityTree.get(1);
* console.log(mit?.name); // 'MIT';
*
* const cmulevel = universityTree.getHeight(4);
* console.log(typeof cmulevel); // 'number';
*
* // Tree maintains strict balance during insertions and deletions
* console.log(universityTree.isAVLBalanced()); // true;
*
* // Add more universities
* universityTree.set(6, { name: 'Oxford', rank: 6, students: 2000 });
* console.log(universityTree.isAVLBalanced()); // true;
*
* // Delete and verify balance is maintained
* universityTree.delete(2);
* console.log(universityTree.has(2)); // false;
* console.log(universityTree.isAVLBalanced()); // true;
*
* // Get all remaining universities in rank order
* const remainingRanks = [...universityTree.keys()];
* console.log(remainingRanks); // [1, 3, 4, 5, 6];
* console.log(universityTree.size); // 5;
* @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 }
* // ];
*/
export class AVLTree<K = any, V = any, R = any> extends BST<K, V, R> implements IBinaryTree<K, V, R> {
/**
* Creates an instance of AVLTree.
* @remarks Time O(N log N) (from `setMany` with balanced set). Space O(N).
*
* @param [keysNodesEntriesOrRaws=[]] - An iterable of items to set.
* @param [options] - Configuration options for the AVL tree.
*/
constructor(
keysNodesEntriesOrRaws: Iterable<
K | AVLTreeNode<K, V> | [K | null | undefined, V | undefined] | null | undefined | R
> = [],
options?: AVLTreeOptions<K, V, R>
) {
super([], options);
// Note: super.setMany is called, which in BST defaults to balanced set.
if (keysNodesEntriesOrRaws) super.setMany(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.
*/
override createNode(key: K, value?: V): AVLTreeNode<K, V> {
return new AVLTreeNode<K, V>(key, value) as AVLTreeNode<K, V>;
}
/**
* 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.
*/
override isNode(
keyNodeOrEntry: K | AVLTreeNode<K, V> | [K | null | undefined, V | undefined] | null | undefined
): keyNodeOrEntry is AVLTreeNode<K, V> {
return keyNodeOrEntry instanceof AVLTreeNode;
}
/**
* Sets a new node to the AVL tree and balances the tree path.
* @remarks Time O(log N) (O(H) for BST set + O(H) for `_balancePath`). Space O(H) for path/recursion.
*
* @param keyNodeOrEntry - The key, node, or entry to set.
* @param [value] - The value, if providing just a key.
* @returns True if the addition was successful, false otherwise.
*/
override set(
keyNodeOrEntry: K | AVLTreeNode<K, V> | [K | null | undefined, V | undefined] | null | undefined,
value?: V
): boolean {
if (keyNodeOrEntry === null) return false;
const inserted = super.set(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.
*/
override delete(
keyNodeOrEntry: K | AVLTreeNode<K, V> | [K | null | undefined, V | undefined] | null | undefined
): BinaryTreeDeleteResult<AVLTreeNode<K, V>>[] {
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.
*/
override perfectlyBalance(iterationType: IterationType = this.iterationType): boolean {
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: number, r: number, parent?: AVLTreeNode<K, V>): AVLTreeNode<K, V> | undefined => {
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 as AVLTreeNode<K, V>).height : -1;
const rh = root.right ? (root.right as AVLTreeNode<K, V>).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) `set` 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.
*/
override map<MK = K, MV = V, MR = any>(
callback: EntryCallback<K, V | undefined, [MK, MV]>,
options?: Partial<BinaryTreeOptions<MK, MV, MR>>,
thisArg?: unknown
): AVLTree<MK, MV, MR> {
const out = this._createLike<MK, MV, MR>([], options);
let index = 0;
// Iterates in-order
for (const [key, value] of this) {
// `set` on the new tree will be O(log N) and will self-balance.
out.set(callback.call(thisArg, value, key, 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.
*/
protected override _createInstance<TK = K, TV = V, TR = R>(options?: Partial<AVLTreeOptions<TK, TV, TR>>): this {
const Ctor = this.constructor as unknown as new (
iter?: Iterable<TK | AVLTreeNode<TK, TV> | [TK | null | undefined, TV | undefined] | null | undefined | TR>,
opts?: AVLTreeOptions<TK, TV, TR>
) => this;
return new Ctor([], { ...this._snapshotOptions<TK, TV, TR>(), ...(options ?? {}) }) as unknown as this;
}
/**
* (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.
*/
protected override _createLike<TK = K, TV = V, TR = R>(
iter: Iterable<TK | AVLTreeNode<TK, TV> | [TK | null | undefined, TV | undefined] | null | undefined | TR> = [],
options?: Partial<AVLTreeOptions<TK, TV, TR>>
): AVLTree<TK, TV, TR> {
const Ctor = this.constructor as unknown as new (
iter?: Iterable<TK | AVLTreeNode<TK, TV> | [TK | null | undefined, TV | undefined] | null | undefined | TR>,
opts?: AVLTreeOptions<TK, TV, TR>
) => AVLTree<TK, TV, TR>;
return new Ctor(iter, { ...this._snapshotOptions<TK, TV, TR>(), ...(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).
*/
protected override _swapProperties(
srcNode: BSTNOptKeyOrNode<K, AVLTreeNode<K, V>>,
destNode: BSTNOptKeyOrNode<K, AVLTreeNode<K, V>>
): AVLTreeNode<K, V> | undefined {
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).
*/
protected _balanceFactor(node: AVLTreeNode<K, V>): number {
const left = node.left ? (node.left as AVLTreeNode<K, V>).height : -1;
const right = node.right ? (node.right as AVLTreeNode<K, V>).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.
*/
protected _updateHeight(node: AVLTreeNode<K, V>): void {
const leftHeight = node.left ? (node.left as AVLTreeNode<K, V>).height : -1;
const rightHeight = node.right ? (node.right as AVLTreeNode<K, V>).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).
*/
protected _balanceLL(A: AVLTreeNode<K, V>): void {
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?.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).
*/
protected _balanceLR(A: AVLTreeNode<K, V>): void {
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).
*/
protected _balanceRR(A: AVLTreeNode<K, V>): void {
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).
*/
protected _balanceRL(A: AVLTreeNode<K, V>): void {
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).
*/
protected _balancePath(node: K | AVLTreeNode<K, V> | [K | null | undefined, V | undefined] | null | undefined): void {
// 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`.
*/
protected override _replaceNode(oldNode: AVLTreeNode<K, V>, newNode: AVLTreeNode<K, V>): AVLTreeNode<K, V> {
// When replacing a node (e.g., on duplicate key), preserve the height.
newNode.height = oldNode.height;
return super._replaceNode(oldNode, newNode);
}
}