data-structure-typed 2.2.7 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/.github/workflows/ci.yml +9 -0
  2. package/CHANGELOG.md +1 -1
  3. package/README.md +14 -3
  4. package/README_CN.md +119 -275
  5. package/benchmark/report.html +1 -1
  6. package/benchmark/report.json +20 -324
  7. package/dist/cjs/index.cjs +689 -182
  8. package/dist/cjs/index.cjs.map +1 -1
  9. package/dist/cjs-legacy/index.cjs +693 -185
  10. package/dist/cjs-legacy/index.cjs.map +1 -1
  11. package/dist/esm/index.mjs +689 -182
  12. package/dist/esm/index.mjs.map +1 -1
  13. package/dist/esm-legacy/index.mjs +693 -185
  14. package/dist/esm-legacy/index.mjs.map +1 -1
  15. package/dist/leetcode/avl-tree-counter.mjs +2957 -0
  16. package/dist/leetcode/avl-tree-multi-map.mjs +2889 -0
  17. package/dist/leetcode/avl-tree.mjs +2720 -0
  18. package/dist/leetcode/binary-tree.mjs +1594 -0
  19. package/dist/leetcode/bst.mjs +2398 -0
  20. package/dist/leetcode/deque.mjs +683 -0
  21. package/dist/leetcode/directed-graph.mjs +1733 -0
  22. package/dist/leetcode/doubly-linked-list.mjs +709 -0
  23. package/dist/leetcode/hash-map.mjs +493 -0
  24. package/dist/leetcode/heap.mjs +542 -0
  25. package/dist/leetcode/max-heap.mjs +375 -0
  26. package/dist/leetcode/max-priority-queue.mjs +383 -0
  27. package/dist/leetcode/min-heap.mjs +363 -0
  28. package/dist/leetcode/min-priority-queue.mjs +371 -0
  29. package/dist/leetcode/priority-queue.mjs +363 -0
  30. package/dist/leetcode/queue.mjs +943 -0
  31. package/dist/leetcode/red-black-tree.mjs +2765 -0
  32. package/dist/leetcode/singly-linked-list.mjs +754 -0
  33. package/dist/leetcode/stack.mjs +217 -0
  34. package/dist/leetcode/tree-counter.mjs +3039 -0
  35. package/dist/leetcode/tree-multi-map.mjs +2913 -0
  36. package/dist/leetcode/trie.mjs +413 -0
  37. package/dist/leetcode/undirected-graph.mjs +1650 -0
  38. package/dist/types/data-structures/base/linear-base.d.ts +6 -6
  39. package/dist/types/data-structures/binary-tree/avl-tree-counter.d.ts +1 -1
  40. package/dist/types/data-structures/binary-tree/avl-tree-multi-map.d.ts +2 -2
  41. package/dist/types/data-structures/binary-tree/avl-tree.d.ts +10 -10
  42. package/dist/types/data-structures/binary-tree/binary-tree.d.ts +25 -27
  43. package/dist/types/data-structures/binary-tree/bst.d.ts +13 -12
  44. package/dist/types/data-structures/binary-tree/red-black-tree.d.ts +151 -21
  45. package/dist/types/data-structures/binary-tree/tree-counter.d.ts +4 -4
  46. package/dist/types/data-structures/binary-tree/tree-multi-map.d.ts +2 -2
  47. package/dist/types/interfaces/binary-tree.d.ts +1 -1
  48. package/dist/umd/data-structure-typed.js +689 -181
  49. package/dist/umd/data-structure-typed.js.map +1 -1
  50. package/dist/umd/data-structure-typed.min.js +3 -3
  51. package/dist/umd/data-structure-typed.min.js.map +1 -1
  52. package/package.json +50 -172
  53. package/src/data-structures/base/linear-base.ts +2 -12
  54. package/src/data-structures/binary-tree/avl-tree-counter.ts +6 -6
  55. package/src/data-structures/binary-tree/avl-tree-multi-map.ts +13 -13
  56. package/src/data-structures/binary-tree/avl-tree.ts +15 -15
  57. package/src/data-structures/binary-tree/binary-tree.ts +57 -60
  58. package/src/data-structures/binary-tree/bst.ts +100 -26
  59. package/src/data-structures/binary-tree/red-black-tree.ts +586 -76
  60. package/src/data-structures/binary-tree/tree-counter.ts +25 -13
  61. package/src/data-structures/binary-tree/tree-multi-map.ts +13 -13
  62. package/src/data-structures/queue/deque.ts +10 -0
  63. package/src/interfaces/binary-tree.ts +1 -1
  64. package/test/performance/data-structures/binary-tree/red-black-tree.test.ts +1 -2
  65. package/test/unit/data-structures/base/iterable-element-base.coverage.test.ts +106 -0
  66. package/test/unit/data-structures/base/iterable-element-base.more-branches.coverage.test.ts +61 -0
  67. package/test/unit/data-structures/base/linear-base.array.coverage.test.ts +168 -0
  68. package/test/unit/data-structures/base/linear-base.concat-else.coverage.test.ts +82 -0
  69. package/test/unit/data-structures/base/linear-base.coverage.test.ts +72 -0
  70. package/test/unit/data-structures/base/linear-base.more-branches.coverage.test.ts +417 -0
  71. package/test/unit/data-structures/binary-tree/avl-tree-counter.more-branches-3.coverage.test.ts +146 -0
  72. package/test/unit/data-structures/binary-tree/avl-tree-counter.more-branches.coverage.test.ts +93 -0
  73. package/test/unit/data-structures/binary-tree/avl-tree-counter.test.ts +30 -30
  74. package/test/unit/data-structures/binary-tree/avl-tree-multi-map.coverage.test.ts +108 -0
  75. package/test/unit/data-structures/binary-tree/avl-tree-multi-map.more-branches-2.coverage.test.ts +85 -0
  76. package/test/unit/data-structures/binary-tree/avl-tree-multi-map.test.ts +46 -46
  77. package/test/unit/data-structures/binary-tree/avl-tree-node.familyPosition-root-left.coverage.test.ts +17 -0
  78. package/test/unit/data-structures/binary-tree/avl-tree.more-branches-2.coverage.test.ts +99 -0
  79. package/test/unit/data-structures/binary-tree/avl-tree.test.ts +43 -43
  80. package/test/unit/data-structures/binary-tree/binary-indexed-tree.more-branches.coverage.test.ts +18 -0
  81. package/test/unit/data-structures/binary-tree/binary-tree.more-branches.coverage.test.ts +56 -0
  82. package/test/unit/data-structures/binary-tree/binary-tree.remaining-branches.coverage.test.ts +229 -0
  83. package/test/unit/data-structures/binary-tree/binary-tree.test.ts +151 -151
  84. package/test/unit/data-structures/binary-tree/bst.bound-by-predicate.coverage.test.ts +33 -0
  85. package/test/unit/data-structures/binary-tree/bst.coverage.test.ts +94 -0
  86. package/test/unit/data-structures/binary-tree/bst.deletebykey.coverage.test.ts +70 -0
  87. package/test/unit/data-structures/binary-tree/bst.deletewhere.coverage.test.ts +37 -0
  88. package/test/unit/data-structures/binary-tree/bst.floor-lower-predicate.coverage.test.ts +29 -0
  89. package/test/unit/data-structures/binary-tree/bst.floor-setmany.coverage.test.ts +72 -0
  90. package/test/unit/data-structures/binary-tree/bst.getnode.range-ensure.coverage.test.ts +22 -0
  91. package/test/unit/data-structures/binary-tree/bst.misc-branches.coverage.test.ts +100 -0
  92. package/test/unit/data-structures/binary-tree/bst.more-branches-2.coverage.test.ts +133 -0
  93. package/test/unit/data-structures/binary-tree/bst.more-branches-3.coverage.test.ts +45 -0
  94. package/test/unit/data-structures/binary-tree/bst.more-branches-4.coverage.test.ts +36 -0
  95. package/test/unit/data-structures/binary-tree/bst.more-branches-5.coverage.test.ts +40 -0
  96. package/test/unit/data-structures/binary-tree/bst.more.coverage.test.ts +39 -0
  97. package/test/unit/data-structures/binary-tree/bst.node-family.coverage.test.ts +29 -0
  98. package/test/unit/data-structures/binary-tree/bst.range-pruning.coverage.test.ts +43 -0
  99. package/test/unit/data-structures/binary-tree/bst.search-fastpath.coverage.test.ts +30 -0
  100. package/test/unit/data-structures/binary-tree/bst.test.ts +124 -154
  101. package/test/unit/data-structures/binary-tree/overall.test.ts +20 -20
  102. package/test/unit/data-structures/binary-tree/red-black-tree.boundary-corruption-repair.coverage.test.ts +66 -0
  103. package/test/unit/data-structures/binary-tree/red-black-tree.boundary-max-update.coverage.test.ts +18 -0
  104. package/test/unit/data-structures/binary-tree/red-black-tree.boundary-null.coverage.test.ts +53 -0
  105. package/test/unit/data-structures/binary-tree/red-black-tree.boundary-stale-cache.coverage.test.ts +25 -0
  106. package/test/unit/data-structures/binary-tree/red-black-tree.boundary-update.coverage.test.ts +23 -0
  107. package/test/unit/data-structures/binary-tree/red-black-tree.cache-delete.coverage.test.ts +49 -0
  108. package/test/unit/data-structures/binary-tree/red-black-tree.cache-edge.coverage.test.ts +37 -0
  109. package/test/unit/data-structures/binary-tree/red-black-tree.cache-stale-insert.coverage.test.ts +39 -0
  110. package/test/unit/data-structures/binary-tree/red-black-tree.coverage.test.ts +334 -0
  111. package/test/unit/data-structures/binary-tree/red-black-tree.delete-fixup.coverage.test.ts +68 -0
  112. package/test/unit/data-structures/binary-tree/red-black-tree.delete-successor.coverage.test.ts +75 -0
  113. package/test/unit/data-structures/binary-tree/red-black-tree.factories.coverage.test.ts +26 -0
  114. package/test/unit/data-structures/binary-tree/red-black-tree.hint-cache-compare-update.coverage.test.ts +74 -0
  115. package/test/unit/data-structures/binary-tree/red-black-tree.hint-cache-no-update.coverage.test.ts +44 -0
  116. package/test/unit/data-structures/binary-tree/red-black-tree.hint-cache-nullish.coverage.test.ts +61 -0
  117. package/test/unit/data-structures/binary-tree/red-black-tree.hint-mapmode-defined.coverage.test.ts +35 -0
  118. package/test/unit/data-structures/binary-tree/red-black-tree.hint-mapmode-undefined.coverage.test.ts +43 -0
  119. package/test/unit/data-structures/binary-tree/red-black-tree.hint-more.coverage.test.ts +99 -0
  120. package/test/unit/data-structures/binary-tree/red-black-tree.hint.coverage.test.ts +60 -0
  121. package/test/unit/data-structures/binary-tree/red-black-tree.insert-cache-nullish.coverage.test.ts +29 -0
  122. package/test/unit/data-structures/binary-tree/red-black-tree.insert-header-parent-nullish.coverage.test.ts +17 -0
  123. package/test/unit/data-structures/binary-tree/red-black-tree.internal-walk.coverage.test.ts +57 -0
  124. package/test/unit/data-structures/binary-tree/red-black-tree.minmax-cache.test.ts +65 -0
  125. package/test/unit/data-structures/binary-tree/red-black-tree.misc-inputs.coverage.test.ts +17 -0
  126. package/test/unit/data-structures/binary-tree/red-black-tree.more-branches-2.coverage.test.ts +121 -0
  127. package/test/unit/data-structures/binary-tree/red-black-tree.more-branches-3.coverage.test.ts +55 -0
  128. package/test/unit/data-structures/binary-tree/red-black-tree.more-branches-4.coverage.test.ts +44 -0
  129. package/test/unit/data-structures/binary-tree/red-black-tree.predsucc.coverage.test.ts +40 -0
  130. package/test/unit/data-structures/binary-tree/red-black-tree.remaining-branches.coverage.test.ts +123 -0
  131. package/test/unit/data-structures/binary-tree/red-black-tree.set-inputs.coverage.test.ts +64 -0
  132. package/test/unit/data-structures/binary-tree/red-black-tree.setkvnode-parent-cache.coverage.test.ts +79 -0
  133. package/test/unit/data-structures/binary-tree/red-black-tree.setkvnode-remaining.coverage.test.ts +44 -0
  134. package/test/unit/data-structures/binary-tree/red-black-tree.setkvnode-uncovered.coverage.test.ts +74 -0
  135. package/test/unit/data-structures/binary-tree/red-black-tree.test.ts +141 -141
  136. package/test/unit/data-structures/binary-tree/red-black-tree.update-branches.coverage.test.ts +30 -0
  137. package/test/unit/data-structures/binary-tree/segment-tree.more-branches.coverage.test.ts +31 -0
  138. package/test/unit/data-structures/binary-tree/tree-counter.coverage.test.ts +115 -0
  139. package/test/unit/data-structures/binary-tree/tree-counter.more-branches.coverage.test.ts +244 -0
  140. package/test/unit/data-structures/binary-tree/tree-counter.test.ts +41 -39
  141. package/test/unit/data-structures/binary-tree/tree-multi-map.coverage.test.ts +104 -0
  142. package/test/unit/data-structures/binary-tree/tree-multi-map.more-branches-2.coverage.test.ts +59 -0
  143. package/test/unit/data-structures/binary-tree/tree-multi-map.test.ts +145 -145
  144. package/test/unit/data-structures/graph/abstract-graph.more-branches-2.coverage.test.ts +40 -0
  145. package/test/unit/data-structures/graph/abstract-graph.more-branches-3.coverage.test.ts +65 -0
  146. package/test/unit/data-structures/graph/abstract-graph.more-branches-4.coverage.test.ts +98 -0
  147. package/test/unit/data-structures/graph/abstract-graph.more-branches-5.coverage.test.ts +51 -0
  148. package/test/unit/data-structures/graph/abstract-graph.more-branches.coverage.test.ts +62 -0
  149. package/test/unit/data-structures/graph/directed-graph.more-branches-2.coverage.test.ts +38 -0
  150. package/test/unit/data-structures/graph/directed-graph.more-branches-3.coverage.test.ts +25 -0
  151. package/test/unit/data-structures/graph/directed-graph.more-branches.coverage.test.ts +82 -0
  152. package/test/unit/data-structures/graph/map-graph.more-branches.coverage.test.ts +22 -0
  153. package/test/unit/data-structures/graph/undirected-graph.more-branches-2.coverage.test.ts +35 -0
  154. package/test/unit/data-structures/graph/undirected-graph.more-branches.coverage.test.ts +87 -0
  155. package/test/unit/data-structures/hash/hash-map.more-branches.coverage.test.ts +64 -0
  156. package/test/unit/data-structures/hash/hash-map.toEntryFn-branch.coverage.test.ts +9 -0
  157. package/test/unit/data-structures/heap/heap.misc-branches.coverage.test.ts +110 -0
  158. package/test/unit/data-structures/heap/heap.remaining-branches.coverage.test.ts +22 -0
  159. package/test/unit/data-structures/heap/max-heap.coverage.test.ts +29 -0
  160. package/test/unit/data-structures/linked-list/doubly-linked-list.more-branches.coverage.test.ts +72 -0
  161. package/test/unit/data-structures/linked-list/linked-list.unshiftMany-else.coverage.test.ts +15 -0
  162. package/test/unit/data-structures/linked-list/singly-linked-list.coverage.test.ts +221 -0
  163. package/test/unit/data-structures/linked-list/singly-linked-list.more-branches.coverage.test.ts +86 -0
  164. package/test/unit/data-structures/linked-list/skip-linked-list.more-branches.coverage.test.ts +31 -0
  165. package/test/unit/data-structures/matrix/matrix.more-branches.coverage.test.ts +81 -0
  166. package/test/unit/data-structures/matrix/matrix.pivotElement-nullish.coverage.test.ts +28 -0
  167. package/test/unit/data-structures/priority-queue/max-priority-queue.more-branches.coverage.test.ts +10 -0
  168. package/test/unit/data-structures/priority-queue/priority-queue.coverage.test.ts +21 -0
  169. package/test/unit/data-structures/queue/deque.coverage.test.ts +173 -0
  170. package/test/unit/data-structures/queue/deque.more-branches-2.coverage.test.ts +39 -0
  171. package/test/unit/data-structures/queue/deque.more-branches-3.coverage.test.ts +9 -0
  172. package/test/unit/data-structures/queue/deque.more-branches.coverage.test.ts +95 -0
  173. package/test/unit/data-structures/queue/queue.coverage.test.ts +138 -0
  174. package/test/unit/data-structures/queue/queue.more-branches-2.coverage.test.ts +27 -0
  175. package/test/unit/data-structures/stack/stack.coverage.test.ts +112 -0
  176. package/test/unit/data-structures/tree/tree.more-branches.coverage.test.ts +9 -0
  177. package/test/unit/data-structures/trie/trie.more-branches-2.coverage.test.ts +51 -0
  178. package/test/utils/patch.ts +33 -0
  179. package/tsup.config.js +50 -21
  180. package/tsup.umd.config.js +29 -0
  181. package/tsup.node.config.js +0 -83
@@ -7,10 +7,10 @@
7
7
  */
8
8
 
9
9
  import type {
10
- BinaryTreeDeleteResult,
10
+ BinaryTreeDeleteResult, BTNRep,
11
11
  CRUD,
12
12
  EntryCallback,
13
- FamilyPosition,
13
+ FamilyPosition, NodePredicate,
14
14
  OptNode,
15
15
  RBTNColor,
16
16
  RedBlackTreeOptions
@@ -24,12 +24,11 @@ export class RedBlackTreeNode<K = any, V = any> {
24
24
  parent?: RedBlackTreeNode<K, V> = undefined;
25
25
 
26
26
  /**
27
- * Create a Red-Black Tree and optionally bulk-insert items.
28
- * @remarks Time O(n log n), Space O(n)
29
- * @param key - See parameter type for details.
30
- * @param [value]- See parameter type for details.
31
- * @param color - See parameter type for details.
32
- * @returns New RedBlackTree instance.
27
+ * Create a Red-Black Tree node.
28
+ * @remarks Time O(1), Space O(1)
29
+ * @param key - Node key.
30
+ * @param [value] - Node value (unused in map mode trees).
31
+ * @param color - Node color.
33
32
  */
34
33
 
35
34
  constructor(key: K, value?: V, color: RBTNColor = 'BLACK') {
@@ -179,7 +178,7 @@ export class RedBlackTreeNode<K = any, V = any> {
179
178
 
180
179
  /**
181
180
  * Represents a Red-Black Tree (self-balancing BST) supporting map-like mode and stable O(log n) updates.
182
- * @remarks Time O(1), Space O(1)
181
+ * @remarks Operation complexity depends on the method; see each method's docs.
183
182
  * @template K
184
183
  * @template V
185
184
  * @template R
@@ -287,17 +286,45 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
287
286
  > = [],
288
287
  options?: RedBlackTreeOptions<K, V, R>
289
288
  ) {
289
+
290
290
  super([], options);
291
291
 
292
292
  this._root = this.NIL;
293
293
 
294
+ // Not part of the actual tree; used only as an internal cache hub.
295
+ this._header = new RedBlackTreeNode<K, V>(undefined as K, undefined, 'BLACK');
296
+ this._header.parent = this.NIL;
297
+ // Avoid using accessors here: they would set NIL.parent and can corrupt sentinel invariants.
298
+ this._header._left = this.NIL;
299
+ this._header._right = this.NIL;
300
+
294
301
  if (keysNodesEntriesOrRaws) {
295
- this.addMany(keysNodesEntriesOrRaws);
302
+ this.setMany(keysNodesEntriesOrRaws);
296
303
  }
297
304
  }
298
305
 
299
306
  protected override _root: RedBlackTreeNode<K, V> | undefined;
300
307
 
308
+ /**
309
+ * (Internal) Header sentinel:
310
+ * - header.parent -> root
311
+ * - header._left -> min (or NIL)
312
+ * - header._right -> max (or NIL)
313
+ *
314
+ * IMPORTANT:
315
+ * - This header is NOT part of the actual tree.
316
+ * - Do NOT use `header.left` / `header.right` accessors for wiring: those setters update `NIL.parent`
317
+ * and can corrupt sentinel invariants / cause hangs. Only touch `header._left/_right`.
318
+ */
319
+ protected _header: RedBlackTreeNode<K, V>;
320
+
321
+ /**
322
+ * (Internal) Cache of the current minimum and maximum nodes.
323
+ * Used for fast-path insert/update when keys are monotonic or near-boundary.
324
+ */
325
+ protected _minNode: RedBlackTreeNode<K, V> | undefined;
326
+ protected _maxNode: RedBlackTreeNode<K, V> | undefined;
327
+
301
328
  /**
302
329
  * Get the current root node.
303
330
  * @remarks Time O(1), Space O(1)
@@ -338,22 +365,451 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
338
365
  * @returns void
339
366
  */
340
367
 
368
+ /**
369
+ * Remove all nodes and clear internal caches.
370
+ * @remarks Time O(n) average, Space O(1)
371
+ */
341
372
  override clear() {
342
373
  super.clear();
343
374
  this._root = this.NIL;
375
+ this._header.parent = this.NIL;
376
+ this._setMinCache(undefined);
377
+ this._setMaxCache(undefined);
378
+ }
379
+
380
+
381
+ /**
382
+ * (Internal) Find a node by key using a tight BST walk (no allocations).
383
+ *
384
+ * NOTE: This uses `header.parent` as the canonical root pointer.
385
+ * @remarks Time O(log n) average, Space O(1)
386
+ */
387
+ protected _findNodeByKey(key: K): RedBlackTreeNode<K, V> | undefined {
388
+ const NIL = this.NIL;
389
+ const cmp = this._compare.bind(this);
390
+
391
+ let cur = (this._header.parent) ?? NIL;
392
+ while (cur !== NIL) {
393
+ const c = cmp(key, cur.key);
394
+ if (c < 0) cur = cur.left ?? NIL;
395
+ else if (c > 0) cur = cur.right ?? NIL;
396
+ else return cur;
397
+ }
398
+ return undefined;
399
+ }
400
+
401
+ /**
402
+ * (Internal) In-order predecessor of a node in a BST.
403
+ * @remarks Time O(log n) average, Space O(1)
404
+ */
405
+ protected _predecessorOf(node: RedBlackTreeNode<K, V>): RedBlackTreeNode<K, V> | undefined {
406
+ const NIL = this.NIL;
407
+ if (node.left && node.left !== NIL) {
408
+ let cur = node.left;
409
+ while (cur.right && cur.right !== NIL) cur = cur.right;
410
+ return cur;
411
+ }
412
+ let cur: RedBlackTreeNode<K, V> | undefined = node;
413
+ let p = node.parent;
414
+ while (p && cur === p.left) {
415
+ cur = p;
416
+ p = p.parent;
417
+ }
418
+ return p;
419
+ }
420
+
421
+ /**
422
+ * (Internal) In-order successor of a node in a BST.
423
+ * @remarks Time O(log n) average, Space O(1)
424
+ */
425
+ protected _successorOf(node: RedBlackTreeNode<K, V>): RedBlackTreeNode<K, V> | undefined {
426
+ const NIL = this.NIL;
427
+ if (node.right && node.right !== NIL) {
428
+ let cur = node.right;
429
+ while (cur.left && cur.left !== NIL) cur = cur.left;
430
+ return cur;
431
+ }
432
+ let cur: RedBlackTreeNode<K, V> | undefined = node;
433
+ let p = node.parent;
434
+ while (p && cur === p.right) {
435
+ cur = p;
436
+ p = p.parent;
437
+ }
438
+ return p;
439
+ }
440
+
441
+ /**
442
+ * (Internal) Attach a new node directly under a known parent/side (no search).
443
+ *
444
+ * This is a performance-oriented helper used by boundary fast paths and hinted insertion.
445
+ * It will:
446
+ * - wire parent/child pointers (using accessors, so parent pointers are updated)
447
+ * - initialize children to NIL
448
+ * - mark the new node RED, then run insert fix-up
449
+ *
450
+ * Precondition: the chosen slot (parent.left/parent.right) is empty (NIL/null/undefined).
451
+ * @remarks Time O(log n) average, Space O(1)
452
+ */
453
+ protected _attachNewNode(parent: RedBlackTreeNode<K, V>, side: 'left' | 'right', node: RedBlackTreeNode<K, V>): void {
454
+ const NIL = this.NIL;
455
+ node.parent = parent;
456
+ if (side === 'left') parent.left = node;
457
+ else parent.right = node;
458
+ node.left = NIL;
459
+ node.right = NIL;
460
+ node.color = 'RED';
461
+ this._insertFixup(node);
462
+ if (this.isRealNode(this._root)) this._root.color = 'BLACK';
463
+ }
464
+
465
+ /**
466
+ * (Internal) a single source of truth for min/max is header._left/_right.
467
+ * Keep legacy _minNode/_maxNode mirrored for compatibility.
468
+ * @remarks Time O(1), Space O(1)
469
+ */
470
+ /**
471
+ * (Internal) Update min cache pointers (header._left is the canonical min pointer).
472
+ * @remarks Time O(1), Space O(1)
473
+ */
474
+ protected _setMinCache(node: RedBlackTreeNode<K, V> | undefined): void {
475
+ this._minNode = node;
476
+ this._header._left = node ?? this.NIL;
477
+ }
478
+
479
+ /**
480
+ * (Internal) Update max cache pointers (header._right is the canonical max pointer).
481
+ * @remarks Time O(1), Space O(1)
482
+ */
483
+ protected _setMaxCache(node: RedBlackTreeNode<K, V> | undefined): void {
484
+ this._maxNode = node;
485
+ this._header._right = node ?? this.NIL;
486
+ }
487
+
488
+ /**
489
+ * (Internal) Core set implementation returning the affected node.
490
+ *
491
+ * Hot path goals:
492
+ * - Avoid double walks (search+insert): do a single traversal that either updates or inserts.
493
+ * - Use header min/max caches to fast-path boundary inserts.
494
+ * - Keep header._left/_right as canonical min/max pointers.
495
+ *
496
+ * Return value:
497
+ * - `{ node, created:false }` when an existing key is updated
498
+ * - `{ node, created:true }` when a new node is inserted
499
+ * - `undefined` only on unexpected internal failure.
500
+ * @remarks Time O(log n) average, Space O(1)
501
+ */
502
+ protected _setKVNode(key: K, nextValue?: V): { node: RedBlackTreeNode<K, V>; created: boolean } | undefined {
503
+ const NIL = this.NIL;
504
+ const comparator = this._comparator;
505
+
506
+ // Read via header to avoid undefined checks (header uses NIL when empty).
507
+ const header = this._header;
508
+ const minN = header._left ?? NIL;
509
+ if (minN !== NIL) {
510
+ const cMin = comparator(key, minN.key);
511
+ if (cMin === 0) {
512
+ if (this._isMapMode) {
513
+ if (nextValue !== undefined) this._store.set(key, nextValue);
514
+ else this._setValue(key, nextValue);
515
+ } else minN.value = nextValue as V;
516
+ return { node: minN, created: false };
517
+ }
518
+ // Boundary attach: if key is smaller than current min and min has no left child.
519
+ // Inline NIL/null/undefined check to avoid isRealNode overhead on hot path.
520
+ const minL = minN.left;
521
+ if (cMin < 0 && (minL === NIL || minL === null || minL === undefined)) {
522
+ const newNode = this.createNode(key, nextValue);
523
+ this._attachNewNode(minN, 'left', newNode);
524
+ if (this._isMapMode) {
525
+ if (nextValue !== undefined) this._store.set(newNode.key, nextValue);
526
+ else this._setValue(newNode.key, nextValue);
527
+ }
528
+ this._size++;
529
+ this._setMinCache(newNode);
530
+ // If max is not initialized yet (tree had 0/1 nodes), mirror max too.
531
+ if (header._right === NIL) this._setMaxCache(newNode);
532
+ return { node: newNode, created: true };
533
+ }
534
+
535
+ // Only touch max when key is not less than min.
536
+ if (cMin > 0) {
537
+ const maxN = header._right ?? NIL;
538
+ // Boundary attach: if key is greater than current max and max has no right child.
539
+ const cMax = comparator(key, maxN.key);
540
+ if (cMax === 0) {
541
+ if (this._isMapMode) {
542
+ if (nextValue !== undefined) this._store.set(key, nextValue);
543
+ else this._setValue(key, nextValue);
544
+ } else maxN.value = nextValue as V;
545
+ return { node: maxN, created: false };
546
+ }
547
+ const maxR = maxN.right;
548
+ if (cMax > 0 && (maxR === NIL || maxR === null || maxR === undefined)) {
549
+ const newNode = this.createNode(key, nextValue);
550
+ this._attachNewNode(maxN, 'right', newNode);
551
+ if (this._isMapMode) {
552
+ if (nextValue !== undefined) this._store.set(newNode.key, nextValue);
553
+ else this._setValue(newNode.key, nextValue);
554
+ }
555
+ this._size++;
556
+ this._setMaxCache(newNode);
557
+ if (header._left === NIL) this._setMinCache(newNode);
558
+ return { node: newNode, created: true };
559
+ }
560
+ }
561
+ }
562
+
563
+ // Normal path: single-pass search + insert/update (avoid double-walking the tree).
564
+ const cmp = comparator;
565
+ const isMapMode = this._isMapMode;
566
+ const store = this._store;
567
+ let current = this._header.parent ?? NIL;
568
+ let parent: RedBlackTreeNode<K, V> | undefined;
569
+ let lastCompared = 0;
570
+
571
+ while (current !== NIL) {
572
+ parent = current;
573
+ lastCompared = cmp(key, current.key);
574
+ if (lastCompared < 0) current = current.left ?? NIL;
575
+ else if (lastCompared > 0) current = current.right ?? NIL;
576
+ else {
577
+ // Update existing.
578
+ if (isMapMode) {
579
+ if (nextValue !== undefined) store.set(key, nextValue);
580
+ else this._setValue(key, nextValue);
581
+ } else {
582
+ current.value = nextValue as V;
583
+ }
584
+ return { node: current, created: false };
585
+ }
586
+ }
587
+
588
+ // Insert new.
589
+ const newNode = this.createNode(key, nextValue);
590
+ // createNode always returns a real node in RedBlackTree.
591
+ newNode.parent = parent;
592
+
593
+ if (!parent) {
594
+ this._setRoot(newNode);
595
+ } else if (lastCompared < 0) {
596
+ parent.left = newNode;
597
+ } else {
598
+ parent.right = newNode;
599
+ }
600
+
601
+ newNode.left = NIL;
602
+ newNode.right = NIL;
603
+ newNode.color = 'RED';
604
+
605
+ this._insertFixup(newNode);
606
+ if (this.isRealNode(this._root)) this._root.color = 'BLACK';
607
+ else return undefined;
608
+
609
+ if (isMapMode) {
610
+ if (nextValue !== undefined) store.set(newNode.key, nextValue);
611
+ else this._setValue(newNode.key, nextValue);
612
+ }
613
+ this._size++;
614
+
615
+ // Maintain min/max caches on insertion (header.left/right are canonical).
616
+ const hMin = this._header._left ?? NIL;
617
+ const hMax = this._header._right ?? NIL;
618
+
619
+ // Fast-path: empty tree or attaching directly to an extreme.
620
+ if (hMin === NIL || hMax === NIL) {
621
+ this._setMinCache(newNode);
622
+ this._setMaxCache(newNode);
623
+ } else if (parent === hMax && lastCompared > 0) {
624
+ this._setMaxCache(newNode);
625
+ } else if (parent === hMin && lastCompared < 0) {
626
+ this._setMinCache(newNode);
627
+ } else {
628
+ if (cmp(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
629
+ if (cmp(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
630
+ }
631
+
632
+ return { node: newNode, created: true };
633
+ }
634
+
635
+ /**
636
+ * (Internal) Boolean wrapper around `_setKVNode`.
637
+ *
638
+ * Includes a map-mode update fast-path:
639
+ * - If `isMapMode=true` and the key already exists in `_store`, then updating the value does not
640
+ * require any tree search/rotation (tree shape depends only on key).
641
+ * - This path is intentionally limited to `nextValue !== undefined` to preserve existing
642
+ * semantics for `undefined` values.
643
+ * @remarks Time O(log n) average, Space O(1)
644
+ */
645
+ protected _setKV(key: K, nextValue?: V): boolean {
646
+ if (this._isMapMode && nextValue !== undefined) {
647
+ const store = this._store;
648
+ if (store.has(key)) {
649
+ store.set(key, nextValue);
650
+ return true;
651
+ }
652
+ }
653
+
654
+ return this._setKVNode(key, nextValue) !== undefined;
344
655
  }
345
656
 
346
657
  /**
347
- * Insert or replace an entry using BST order and red-black fix-up.
348
- * @remarks Time O(log n), Space O(1)
349
- * @param keyNodeOrEntry - Key, node, or [key, value] entry to insert.
350
- * @param [value]- See parameter type for details.
351
- * @returns True if inserted or updated; false if ignored.
658
+ * Insert/update using a hint node to speed up nearby insertions.
659
+ *
660
+ * close to the expected insertion position (often the previously returned node in a loop).
661
+ *
662
+ * When the hint is a good fit (sorted / nearly-sorted insertion), this can avoid most of the
663
+ * normal root-to-leaf search and reduce constant factors.
664
+ *
665
+ * When the hint does not match (random workloads), this will fall back to the normal set path.
666
+ * @remarks Time O(log n) average, Space O(1)
667
+ */
668
+ setWithHintNode(key: K, value: V, hint?: RedBlackTreeNode<K, V>): RedBlackTreeNode<K, V> | undefined {
669
+ if (!hint || !this.isRealNode(hint)) {
670
+ return this._setKVNode(key, value)?.node;
671
+ }
672
+
673
+ const cmp = this._compare.bind(this);
674
+ const c0 = cmp(key, hint.key);
675
+ if (c0 === 0) {
676
+ if (this._isMapMode) {
677
+ if (value !== undefined) this._store.set(key , value );
678
+ else this._setValue(key, value);
679
+ } else hint.value = value;
680
+ return hint;
681
+ }
682
+
683
+ if (c0 < 0) {
684
+ // Ultra-fast path: direct attach if the target slot is empty.
685
+ if (!this.isRealNode(hint.left)) {
686
+ const newNode = this.createNode(key, value);
687
+ if (!this.isRealNode(newNode)) return undefined;
688
+ this._attachNewNode(hint, 'left', newNode);
689
+ if (this._isMapMode) {
690
+ if (value !== undefined) this._store.set(key , value );
691
+ else this._setValue(key, value);
692
+ }
693
+ this._size++;
694
+ // Maintain header/min/max caches.
695
+ const NIL = this.NIL;
696
+ const hMin = this._header._left ?? NIL;
697
+ if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
698
+ const hMax = this._header._right ?? NIL;
699
+ if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
700
+ return newNode;
701
+ }
702
+
703
+ const pred = this._predecessorOf(hint);
704
+ if (pred && cmp(pred.key, key) >= 0) {
705
+ return this._setKVNode(key, value)?.node;
706
+ }
707
+
708
+ // Try attach as right of pred.
709
+ if (pred && !this.isRealNode(pred.right)) {
710
+ const newNode = this.createNode(key, value);
711
+ if (!this.isRealNode(newNode)) return undefined;
712
+ this._attachNewNode(pred, 'right', newNode);
713
+ if (this._isMapMode) {
714
+ if (value !== undefined) this._store.set(key , value );
715
+ else this._setValue(key, value);
716
+ }
717
+ this._size++;
718
+ // Maintain header/min/max caches.
719
+ const NIL = this.NIL;
720
+ const hMin = this._header._left ?? NIL;
721
+ if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
722
+ const hMax = this._header._right ?? NIL;
723
+ if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
724
+ return newNode;
725
+ }
726
+
727
+ return this._setKVNode(key, value)?.node;
728
+ }
729
+
730
+ // c0 > 0
731
+ // Ultra-fast path: direct attach if the target slot is empty.
732
+ if (!this.isRealNode(hint.right)) {
733
+ const newNode = this.createNode(key, value);
734
+ if (!this.isRealNode(newNode)) return undefined;
735
+ this._attachNewNode(hint, 'right', newNode);
736
+ if (this._isMapMode) {
737
+ if (value !== undefined) this._store.set(key , value );
738
+ else this._setValue(key, value);
739
+ }
740
+ this._size++;
741
+ // Maintain header/min/max caches.
742
+ const NIL = this.NIL;
743
+ const hMin = this._header._left ?? NIL;
744
+ if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
745
+ const hMax = this._header._right ?? NIL;
746
+ if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
747
+ return newNode;
748
+ }
749
+
750
+ const succ = this._successorOf(hint);
751
+ if (succ && cmp(succ.key, key) <= 0) {
752
+ return this._setKVNode(key, value)?.node;
753
+ }
754
+
755
+ if (succ && !this.isRealNode(succ.left)) {
756
+ const newNode = this.createNode(key, value);
757
+ if (!this.isRealNode(newNode)) return undefined;
758
+ this._attachNewNode(succ, 'left', newNode);
759
+ if (this._isMapMode) {
760
+ if (value !== undefined) this._store.set(key , value );
761
+ else this._setValue(key, value);
762
+ }
763
+ this._size++;
764
+ // Maintain header/min/max caches.
765
+ const NIL = this.NIL;
766
+ const hMin = this._header._left ?? NIL;
767
+ if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
768
+ const hMax = this._header._right ?? NIL;
769
+ if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
770
+ return newNode;
771
+ }
772
+
773
+ return this._setKVNode(key, value)?.node;
774
+ }
775
+
776
+ /**
777
+ * Boolean wrapper for setWithHintNode.
778
+ * @remarks Time O(log n) average, Space O(1)
779
+ */
780
+ setWithHint(key: K, value: V, hint?: RedBlackTreeNode<K, V>): boolean {
781
+ return this.setWithHintNode(key, value, hint) !== undefined;
782
+ }
783
+
784
+ /**
785
+ * Insert or update a key/value (map mode) or key-only (set mode).
786
+ *
787
+ * This method is optimized for:
788
+ * - monotonic inserts via min/max boundary fast paths
789
+ * - updates via a single-pass search (no double walk)
790
+ *
791
+ * @remarks Time O(log n) average, Space O(1)
352
792
  */
353
- override add(
793
+ override set(
354
794
  keyNodeOrEntry: K | RedBlackTreeNode<K, V> | [K | null | undefined, V | undefined] | null | undefined,
355
795
  value?: V
356
796
  ): boolean {
797
+ // Common path: tree.set(key, value) or tree.set([key, value]).
798
+ if (!this.isNode(keyNodeOrEntry)) {
799
+ if (keyNodeOrEntry === null || keyNodeOrEntry === undefined) return false;
800
+
801
+ if (this.isEntry(keyNodeOrEntry)) {
802
+ const key = keyNodeOrEntry[0];
803
+ if (key === null || key === undefined) return false;
804
+ const nextValue = value ?? keyNodeOrEntry[1];
805
+ return this._setKV(key, nextValue);
806
+ }
807
+
808
+ // key-only
809
+ return this._setKV(keyNodeOrEntry, value);
810
+ }
811
+
812
+ // Node insertion path (advanced usage)
357
813
  const [newNode, newValue] = this._keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value);
358
814
  if (!this.isRealNode(newNode)) return false;
359
815
 
@@ -378,25 +834,30 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
378
834
 
379
835
  /**
380
836
  * Delete a node by key/node/entry and rebalance as needed.
381
- * @remarks Time O(log n), Space O(1)
382
- * @param keyNodeOrEntry - Key, node, or [key, value] entry identifying the node to delete.
837
+ * @remarks Time O(log n) average, Space O(1)
838
+ * @param keyNodeEntryRawOrPredicate - Key, node, or [key, value] entry identifying the node to delete.
383
839
  * @returns Array with deletion metadata (removed node, rebalancing hint if any).
384
840
  */
385
-
386
841
  override delete(
387
- keyNodeOrEntry: K | RedBlackTreeNode<K, V> | [K | null | undefined, V | undefined] | null | undefined
842
+ keyNodeEntryRawOrPredicate: BTNRep<K, V, RedBlackTreeNode<K, V>> | NodePredicate<RedBlackTreeNode<K, V> | null>
388
843
  ): BinaryTreeDeleteResult<RedBlackTreeNode<K, V>>[] {
389
- if (keyNodeOrEntry === null) return [];
844
+ if (keyNodeEntryRawOrPredicate === null) return [];
390
845
 
391
846
  const results: BinaryTreeDeleteResult<RedBlackTreeNode<K, V>>[] = [];
392
847
  let nodeToDelete: OptNode<RedBlackTreeNode<K, V>>;
393
- if (this._isPredicate(keyNodeOrEntry)) nodeToDelete = this.getNode(keyNodeOrEntry);
394
- else nodeToDelete = this.isRealNode(keyNodeOrEntry) ? keyNodeOrEntry : this.getNode(keyNodeOrEntry);
848
+ if (this._isPredicate(keyNodeEntryRawOrPredicate)) nodeToDelete = this.getNode(keyNodeEntryRawOrPredicate);
849
+ else nodeToDelete = this.isRealNode(keyNodeEntryRawOrPredicate) ? keyNodeEntryRawOrPredicate : this.getNode(keyNodeEntryRawOrPredicate);
395
850
 
396
851
  if (!nodeToDelete) {
397
852
  return results;
398
853
  }
399
854
 
855
+ // Track min/max cache updates before structural modifications.
856
+ const willDeleteMin = nodeToDelete === this._minNode;
857
+ const willDeleteMax = nodeToDelete === this._maxNode;
858
+ const nextMin = willDeleteMin ? this._successorOf(nodeToDelete) : undefined;
859
+ const nextMax = willDeleteMax ? this._predecessorOf(nodeToDelete) : undefined;
860
+
400
861
  let originalColor = nodeToDelete.color;
401
862
  let replacementNode: RedBlackTreeNode<K, V> | undefined;
402
863
 
@@ -439,6 +900,22 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
439
900
  if (this._isMapMode) this._store.delete(nodeToDelete.key);
440
901
  this._size--;
441
902
 
903
+ // Update min/max caches.
904
+ if (this._size <= 0) {
905
+ this._setMinCache(undefined);
906
+ this._setMaxCache(undefined);
907
+ } else {
908
+ if (willDeleteMin) this._setMinCache(nextMin);
909
+ if (willDeleteMax) this._setMaxCache(nextMax);
910
+ // Fallback if successor/predecessor was unavailable.
911
+ if (!this._minNode || !this.isRealNode(this._minNode)) {
912
+ this._setMinCache(this.isRealNode(this._root) ? this.getLeftMost(n => n, this._root) : undefined);
913
+ }
914
+ if (!this._maxNode || !this.isRealNode(this._maxNode)) {
915
+ this._setMaxCache(this.isRealNode(this._root) ? this.getRightMost(n => n, this._root) : undefined);
916
+ }
917
+ }
918
+
442
919
  if (originalColor === 'BLACK') {
443
920
  this._deleteFixup(replacementNode);
444
921
  }
@@ -450,7 +927,7 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
450
927
 
451
928
  /**
452
929
  * Transform entries into a like-kind red-black tree with possibly different key/value types.
453
- * @remarks Time O(n), Space O(n)
930
+ * @remarks Time O(n) average, Space O(n)
454
931
  * @template MK
455
932
  * @template MV
456
933
  * @template MR
@@ -459,7 +936,6 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
459
936
  * @param [thisArg] - See parameter type for details.
460
937
  * @returns A new RedBlackTree with mapped entries.
461
938
  */
462
-
463
939
  override map<MK = K, MV = V, MR = any>(
464
940
  callback: EntryCallback<K, V | undefined, [MK, MV]>,
465
941
  options?: Partial<RedBlackTreeOptions<MK, MV, MR>>,
@@ -469,11 +945,15 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
469
945
 
470
946
  let index = 0;
471
947
  for (const [key, value] of this) {
472
- out.add(callback.call(thisArg, value, key, index++, this));
948
+ out.set(callback.call(thisArg, value, key, index++, this));
473
949
  }
474
950
  return out;
475
951
  }
476
952
 
953
+ /**
954
+ * (Internal) Create an empty instance of the same concrete tree type.
955
+ * @remarks Time O(1) average, Space O(1)
956
+ */
477
957
  protected override _createInstance<TK = K, TV = V, TR = R>(options?: Partial<RedBlackTreeOptions<TK, TV, TR>>): this {
478
958
  const Ctor = this.constructor as unknown as new (
479
959
  iter?: Iterable<TK | RedBlackTreeNode<TK, TV> | [TK | null | undefined, TV | undefined] | null | undefined | TR>,
@@ -482,6 +962,10 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
482
962
  return new Ctor([], { ...this._snapshotOptions<TK, TV, TR>(), ...(options ?? {}) }) as unknown as this;
483
963
  }
484
964
 
965
+ /**
966
+ * (Internal) Create a like-kind tree (same concrete class) populated from an iterable.
967
+ * @remarks Time O(m log m) average (m = iterable length), Space O(m)
968
+ */
485
969
  protected override _createLike<TK = K, TV = V, TR = R>(
486
970
  iter: Iterable<
487
971
  TK | RedBlackTreeNode<TK, TV> | [TK | null | undefined, TV | undefined] | null | undefined | TR
@@ -495,13 +979,25 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
495
979
  return new Ctor(iter, { ...this._snapshotOptions<TK, TV, TR>(), ...(options ?? {}) });
496
980
  }
497
981
 
982
+ /**
983
+ * (Internal) Set the root pointer and keep header.parent in sync.
984
+ * @remarks Time O(1), Space O(1)
985
+ */
498
986
  protected override _setRoot(v: RedBlackTreeNode<K, V> | undefined) {
987
+ const NIL = this.NIL;
499
988
  if (v) {
500
989
  v.parent = undefined;
501
990
  }
502
991
  this._root = v;
992
+ // Keep header root pointer in sync even for internal operations (rotations/transplants)
993
+ // and for subclasses that may bypass _setKVNode.
994
+ this._header.parent = v ?? NIL;
503
995
  }
504
996
 
997
+ /**
998
+ * (Internal) Replace a node in place while preserving its color.
999
+ * @remarks Time O(1) average, Space O(1)
1000
+ */
505
1001
  protected override _replaceNode(
506
1002
  oldNode: RedBlackTreeNode<K, V>,
507
1003
  newNode: RedBlackTreeNode<K, V>
@@ -513,22 +1009,25 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
513
1009
 
514
1010
  /**
515
1011
  * (Protected) Standard BST insert followed by red-black fix-up.
516
- * @remarks Time O(log n), Space O(1)
1012
+ * @remarks Time O(log n) average, Space O(1)
517
1013
  * @param node - Node to insert.
518
1014
  * @returns Status string: 'CREATED' or 'UPDATED'.
519
1015
  */
520
-
521
1016
  protected _insert(node: RedBlackTreeNode<K, V>): CRUD {
522
- let current = this.root ?? this.NIL;
523
- let parent: RedBlackTreeNode<K, V> | undefined = undefined;
1017
+ const NIL = this.NIL;
1018
+ const cmp = this._compare.bind(this);
1019
+
1020
+ let current = this._header.parent ?? NIL;
1021
+ let parent: RedBlackTreeNode<K, V> | undefined;
1022
+ let lastCompared = 0;
524
1023
 
525
- while (current !== this.NIL) {
1024
+ while (current !== NIL) {
526
1025
  parent = current;
527
- const compared = this._compare(node.key, current.key);
528
- if (compared < 0) {
529
- current = current.left ?? this.NIL;
530
- } else if (compared > 0) {
531
- current = current.right ?? this.NIL;
1026
+ lastCompared = cmp(node.key, current.key);
1027
+ if (lastCompared < 0) {
1028
+ current = current.left ?? NIL;
1029
+ } else if (lastCompared > 0) {
1030
+ current = current.right ?? NIL;
532
1031
  } else {
533
1032
  this._replaceNode(current, node);
534
1033
  return 'UPDATED';
@@ -539,14 +1038,14 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
539
1038
 
540
1039
  if (!parent) {
541
1040
  this._setRoot(node);
542
- } else if (this._compare(node.key, parent.key) < 0) {
1041
+ } else if (lastCompared < 0) {
543
1042
  parent.left = node;
544
1043
  } else {
545
1044
  parent.right = node;
546
1045
  }
547
1046
 
548
- node.left = this.NIL;
549
- node.right = this.NIL;
1047
+ node.left = NIL;
1048
+ node.right = NIL;
550
1049
  node.color = 'RED';
551
1050
 
552
1051
  this._insertFixup(node);
@@ -560,7 +1059,6 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
560
1059
  * @param v - Replacement subtree root (may be undefined).
561
1060
  * @returns void
562
1061
  */
563
-
564
1062
  protected _transplant(u: RedBlackTreeNode<K, V>, v: RedBlackTreeNode<K, V> | undefined): void {
565
1063
  if (!u.parent) {
566
1064
  this._setRoot(v);
@@ -577,53 +1075,68 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
577
1075
 
578
1076
  /**
579
1077
  * (Protected) Restore red-black properties after insertion (recolor/rotate).
580
- * @remarks Time O(log n), Space O(1)
1078
+ * @remarks Time O(log n) average, Space O(1)
581
1079
  * @param z - Recently inserted node.
582
1080
  * @returns void
583
1081
  */
584
-
585
1082
  protected _insertFixup(z: RedBlackTreeNode<K, V> | undefined): void {
586
- while (z?.parent?.color === 'RED') {
587
- if (z.parent === z.parent.parent?.left) {
588
- const y = z.parent.parent.right;
1083
+ const leftRotate = this._leftRotate.bind(this);
1084
+ const rightRotate = this._rightRotate.bind(this);
1085
+
1086
+ while (z) {
1087
+ const p = z.parent;
1088
+ if (!p || p.color !== 'RED') break;
1089
+
1090
+ const gp = p.parent;
1091
+ if (!gp) break;
1092
+
1093
+ if (p === gp.left) {
1094
+ const y = gp.right;
589
1095
  if (y?.color === 'RED') {
590
- z.parent.color = 'BLACK';
1096
+ p.color = 'BLACK';
591
1097
  y.color = 'BLACK';
592
- z.parent.parent.color = 'RED';
1098
+ gp.color = 'RED';
1099
+ z = gp;
1100
+ continue;
1101
+ }
593
1102
 
594
- z = z.parent.parent;
595
- } else {
596
- if (z === z.parent.right) {
597
- z = z.parent;
598
- this._leftRotate(z);
599
- }
1103
+ if (z === p.right) {
1104
+ z = p;
1105
+ leftRotate(z);
1106
+ }
600
1107
 
601
- if (z && z.parent && z.parent.parent) {
602
- z.parent.color = 'BLACK';
603
- z.parent.parent.color = 'RED';
604
- this._rightRotate(z.parent.parent);
605
- }
1108
+ const p2 = z?.parent;
1109
+ const gp2 = p2?.parent;
1110
+ if (p2 && gp2) {
1111
+ p2.color = 'BLACK';
1112
+ gp2.color = 'RED';
1113
+ rightRotate(gp2);
606
1114
  }
607
1115
  } else {
608
- const y: RedBlackTreeNode<K, V> | undefined = z?.parent?.parent?.left ?? undefined;
1116
+ const y = gp.left;
609
1117
  if (y?.color === 'RED') {
610
- z.parent.color = 'BLACK';
1118
+ p.color = 'BLACK';
611
1119
  y.color = 'BLACK';
612
- z.parent.parent!.color = 'RED';
613
- z = z.parent.parent;
614
- } else {
615
- if (z === z.parent.left) {
616
- z = z.parent;
617
- this._rightRotate(z);
618
- }
1120
+ gp.color = 'RED';
1121
+ z = gp;
1122
+ continue;
1123
+ }
619
1124
 
620
- if (z && z.parent && z.parent.parent) {
621
- z.parent.color = 'BLACK';
622
- z.parent.parent.color = 'RED';
623
- this._leftRotate(z.parent.parent);
624
- }
1125
+ if (z === p.left) {
1126
+ z = p;
1127
+ rightRotate(z);
1128
+ }
1129
+
1130
+ const p2 = z?.parent;
1131
+ const gp2 = p2?.parent;
1132
+ if (p2 && gp2) {
1133
+ p2.color = 'BLACK';
1134
+ gp2.color = 'RED';
1135
+ leftRotate(gp2);
625
1136
  }
626
1137
  }
1138
+
1139
+ break;
627
1140
  }
628
1141
 
629
1142
  if (this.isRealNode(this._root)) this._root.color = 'BLACK';
@@ -631,11 +1144,10 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
631
1144
 
632
1145
  /**
633
1146
  * (Protected) Restore red-black properties after deletion (recolor/rotate).
634
- * @remarks Time O(log n), Space O(1)
1147
+ * @remarks Time O(log n) average, Space O(1)
635
1148
  * @param node - Child that replaced the deleted node (may be undefined).
636
1149
  * @returns void
637
1150
  */
638
-
639
1151
  protected _deleteFixup(node: RedBlackTreeNode<K, V> | undefined): void {
640
1152
  if (!node || node === this.root || node.color === 'BLACK') {
641
1153
  if (node) {
@@ -705,7 +1217,6 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
705
1217
  * @param x - Pivot node to rotate around.
706
1218
  * @returns void
707
1219
  */
708
-
709
1220
  protected _leftRotate(x: RedBlackTreeNode<K, V> | undefined): void {
710
1221
  if (!x || !x.right) {
711
1222
  return;
@@ -738,7 +1249,6 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
738
1249
  * @param y - Pivot node to rotate around.
739
1250
  * @returns void
740
1251
  */
741
-
742
1252
  protected _rightRotate(y: RedBlackTreeNode<K, V> | undefined): void {
743
1253
  if (!y || !y.left) {
744
1254
  return;