max-priority-queue-typed 2.4.0 → 2.4.1

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 (43) hide show
  1. package/dist/types/data-structures/base/linear-base.d.ts +6 -6
  2. package/dist/types/data-structures/binary-tree/binary-tree.d.ts +6 -6
  3. package/dist/types/data-structures/binary-tree/bst.d.ts +2 -1
  4. package/dist/types/data-structures/binary-tree/index.d.ts +3 -3
  5. package/dist/types/data-structures/binary-tree/red-black-tree.d.ts +150 -20
  6. package/dist/types/data-structures/binary-tree/tree-map.d.ts +188 -0
  7. package/dist/types/data-structures/binary-tree/tree-multi-map.d.ts +238 -147
  8. package/dist/types/data-structures/binary-tree/tree-multi-set.d.ts +270 -0
  9. package/dist/types/data-structures/binary-tree/tree-set.d.ts +181 -0
  10. package/dist/types/interfaces/binary-tree.d.ts +2 -2
  11. package/dist/types/types/data-structures/binary-tree/index.d.ts +3 -3
  12. package/dist/types/types/data-structures/binary-tree/tree-map.d.ts +33 -0
  13. package/dist/types/types/data-structures/binary-tree/tree-multi-set.d.ts +16 -0
  14. package/dist/types/types/data-structures/binary-tree/tree-set.d.ts +33 -0
  15. package/package.json +2 -2
  16. package/src/data-structures/base/linear-base.ts +2 -12
  17. package/src/data-structures/binary-tree/avl-tree.ts +1 -1
  18. package/src/data-structures/binary-tree/binary-tree.ts +45 -21
  19. package/src/data-structures/binary-tree/bst.ts +85 -10
  20. package/src/data-structures/binary-tree/index.ts +3 -3
  21. package/src/data-structures/binary-tree/red-black-tree.ts +568 -76
  22. package/src/data-structures/binary-tree/tree-map.ts +439 -0
  23. package/src/data-structures/binary-tree/tree-multi-map.ts +488 -325
  24. package/src/data-structures/binary-tree/tree-multi-set.ts +502 -0
  25. package/src/data-structures/binary-tree/tree-set.ts +407 -0
  26. package/src/data-structures/queue/deque.ts +10 -0
  27. package/src/interfaces/binary-tree.ts +2 -2
  28. package/src/types/data-structures/binary-tree/index.ts +3 -3
  29. package/src/types/data-structures/binary-tree/tree-map.ts +45 -0
  30. package/src/types/data-structures/binary-tree/tree-multi-set.ts +19 -0
  31. package/src/types/data-structures/binary-tree/tree-set.ts +39 -0
  32. package/dist/types/data-structures/binary-tree/avl-tree-counter.d.ts +0 -236
  33. package/dist/types/data-structures/binary-tree/avl-tree-multi-map.d.ts +0 -197
  34. package/dist/types/data-structures/binary-tree/tree-counter.d.ts +0 -243
  35. package/dist/types/types/data-structures/binary-tree/avl-tree-counter.d.ts +0 -2
  36. package/dist/types/types/data-structures/binary-tree/avl-tree-multi-map.d.ts +0 -2
  37. package/dist/types/types/data-structures/binary-tree/tree-counter.d.ts +0 -2
  38. package/src/data-structures/binary-tree/avl-tree-counter.ts +0 -539
  39. package/src/data-structures/binary-tree/avl-tree-multi-map.ts +0 -438
  40. package/src/data-structures/binary-tree/tree-counter.ts +0 -575
  41. package/src/types/data-structures/binary-tree/avl-tree-counter.ts +0 -3
  42. package/src/types/data-structures/binary-tree/avl-tree-multi-map.ts +0 -3
  43. package/src/types/data-structures/binary-tree/tree-counter.ts +0 -3
@@ -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,10 +286,18 @@ 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
302
  this.setMany(keysNodesEntriesOrRaws);
296
303
  }
@@ -298,6 +305,26 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
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)
@@ -316,7 +343,7 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
316
343
  * @returns A new RedBlackTreeNode instance.
317
344
  */
318
345
  override createNode(key: K, value?: V, color: RBTNColor = 'BLACK'): RedBlackTreeNode<K, V> {
319
- return new RedBlackTreeNode<K, V>(key, this._isMapMode ? undefined : value, color);
346
+ return new RedBlackTreeNode<K, V>(key, value, color);
320
347
  }
321
348
 
322
349
  /**
@@ -338,22 +365,421 @@ 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
+ minN.value = nextValue as V;
513
+ if (this._isMapMode) this._store.set(key, minN);
514
+ return { node: minN, created: false };
515
+ }
516
+ // Boundary attach: if key is smaller than current min and min has no left child.
517
+ // Inline NIL/null/undefined check to avoid isRealNode overhead on hot path.
518
+ const minL = minN.left;
519
+ if (cMin < 0 && (minL === NIL || minL === null || minL === undefined)) {
520
+ const newNode = this.createNode(key, nextValue);
521
+ this._attachNewNode(minN, 'left', newNode);
522
+ if (this._isMapMode) this._store.set(newNode.key, newNode);
523
+ this._size++;
524
+ this._setMinCache(newNode);
525
+ // If max is not initialized yet (tree had 0/1 nodes), mirror max too.
526
+ if (header._right === NIL) this._setMaxCache(newNode);
527
+ return { node: newNode, created: true };
528
+ }
529
+
530
+ // Only touch max when key is not less than min.
531
+ if (cMin > 0) {
532
+ const maxN = header._right ?? NIL;
533
+ // Boundary attach: if key is greater than current max and max has no right child.
534
+ const cMax = comparator(key, maxN.key);
535
+ if (cMax === 0) {
536
+ maxN.value = nextValue as V;
537
+ if (this._isMapMode) this._store.set(key, maxN);
538
+ return { node: maxN, created: false };
539
+ }
540
+ const maxR = maxN.right;
541
+ if (cMax > 0 && (maxR === NIL || maxR === null || maxR === undefined)) {
542
+ const newNode = this.createNode(key, nextValue);
543
+ this._attachNewNode(maxN, 'right', newNode);
544
+ if (this._isMapMode) this._store.set(newNode.key, newNode);
545
+ this._size++;
546
+ this._setMaxCache(newNode);
547
+ if (header._left === NIL) this._setMinCache(newNode);
548
+ return { node: newNode, created: true };
549
+ }
550
+ }
551
+ }
552
+
553
+ // Normal path: single-pass search + insert/update (avoid double-walking the tree).
554
+ const cmp = comparator;
555
+ const isMapMode = this._isMapMode;
556
+ const store = this._store;
557
+ let current = this._header.parent ?? NIL;
558
+ let parent: RedBlackTreeNode<K, V> | undefined;
559
+ let lastCompared = 0;
560
+
561
+ while (current !== NIL) {
562
+ parent = current;
563
+ lastCompared = cmp(key, current.key);
564
+ if (lastCompared < 0) current = current.left ?? NIL;
565
+ else if (lastCompared > 0) current = current.right ?? NIL;
566
+ else {
567
+ // Update existing.
568
+ current.value = nextValue as V;
569
+ if (isMapMode) store.set(key, current);
570
+ return { node: current, created: false };
571
+ }
572
+ }
573
+
574
+ // Insert new.
575
+ const newNode = this.createNode(key, nextValue);
576
+ // createNode always returns a real node in RedBlackTree.
577
+ newNode.parent = parent;
578
+
579
+ if (!parent) {
580
+ this._setRoot(newNode);
581
+ } else if (lastCompared < 0) {
582
+ parent.left = newNode;
583
+ } else {
584
+ parent.right = newNode;
585
+ }
586
+
587
+ newNode.left = NIL;
588
+ newNode.right = NIL;
589
+ newNode.color = 'RED';
590
+
591
+ this._insertFixup(newNode);
592
+ if (this.isRealNode(this._root)) this._root.color = 'BLACK';
593
+ else return undefined;
594
+
595
+ if (isMapMode) store.set(newNode.key, newNode);
596
+ this._size++;
597
+
598
+ // Maintain min/max caches on insertion (header.left/right are canonical).
599
+ const hMin = this._header._left ?? NIL;
600
+ const hMax = this._header._right ?? NIL;
601
+
602
+ // Fast-path: empty tree or attaching directly to an extreme.
603
+ if (hMin === NIL || hMax === NIL) {
604
+ this._setMinCache(newNode);
605
+ this._setMaxCache(newNode);
606
+ } else if (parent === hMax && lastCompared > 0) {
607
+ this._setMaxCache(newNode);
608
+ } else if (parent === hMin && lastCompared < 0) {
609
+ this._setMinCache(newNode);
610
+ } else {
611
+ if (cmp(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
612
+ if (cmp(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
613
+ }
614
+
615
+ return { node: newNode, created: true };
344
616
  }
345
617
 
346
618
  /**
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.
619
+ * (Internal) Boolean wrapper around `_setKVNode`.
620
+ *
621
+ * Includes a map-mode update fast-path:
622
+ * - If `isMapMode=true` and the key already exists in `_store`, then updating the value does not
623
+ * require any tree search/rotation (tree shape depends only on key).
624
+ * - This path is intentionally limited to `nextValue !== undefined` to preserve existing
625
+ * semantics for `undefined` values.
626
+ * @remarks Time O(log n) average, Space O(1)
627
+ */
628
+ protected _setKV(key: K, nextValue?: V): boolean {
629
+ if (this._isMapMode) {
630
+ const store = this._store;
631
+ const node = store.get(key);
632
+ if (node) {
633
+ node.value = nextValue as V;
634
+ return true;
635
+ }
636
+ }
637
+
638
+ return this._setKVNode(key, nextValue) !== undefined;
639
+ }
640
+
641
+ /**
642
+ * Insert/update using a hint node to speed up nearby insertions.
643
+ *
644
+ * close to the expected insertion position (often the previously returned node in a loop).
645
+ *
646
+ * When the hint is a good fit (sorted / nearly-sorted insertion), this can avoid most of the
647
+ * normal root-to-leaf search and reduce constant factors.
648
+ *
649
+ * When the hint does not match (random workloads), this will fall back to the normal set path.
650
+ * @remarks Time O(log n) average, Space O(1)
651
+ */
652
+ setWithHintNode(key: K, value: V, hint?: RedBlackTreeNode<K, V>): RedBlackTreeNode<K, V> | undefined {
653
+ if (!hint || !this.isRealNode(hint)) {
654
+ return this._setKVNode(key, value)?.node;
655
+ }
656
+
657
+ const cmp = this._compare.bind(this);
658
+ const c0 = cmp(key, hint.key);
659
+ if (c0 === 0) {
660
+ hint.value = value;
661
+ if (this._isMapMode) this._store.set(key, hint);
662
+ return hint;
663
+ }
664
+
665
+ if (c0 < 0) {
666
+ // Ultra-fast path: direct attach if the target slot is empty.
667
+ if (!this.isRealNode(hint.left)) {
668
+ const newNode = this.createNode(key, value);
669
+ if (!this.isRealNode(newNode)) return undefined;
670
+ this._attachNewNode(hint, 'left', newNode);
671
+ if (this._isMapMode) this._store.set(key, newNode);
672
+ this._size++;
673
+ // Maintain header/min/max caches.
674
+ const NIL = this.NIL;
675
+ const hMin = this._header._left ?? NIL;
676
+ if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
677
+ const hMax = this._header._right ?? NIL;
678
+ if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
679
+ return newNode;
680
+ }
681
+
682
+ const pred = this._predecessorOf(hint);
683
+ if (pred && cmp(pred.key, key) >= 0) {
684
+ return this._setKVNode(key, value)?.node;
685
+ }
686
+
687
+ // Try attach as right of pred.
688
+ if (pred && !this.isRealNode(pred.right)) {
689
+ const newNode = this.createNode(key, value);
690
+ if (!this.isRealNode(newNode)) return undefined;
691
+ this._attachNewNode(pred, 'right', newNode);
692
+ if (this._isMapMode) this._store.set(key, newNode);
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
+ return this._setKVNode(key, value)?.node;
704
+ }
705
+
706
+ // c0 > 0
707
+ // Ultra-fast path: direct attach if the target slot is empty.
708
+ if (!this.isRealNode(hint.right)) {
709
+ const newNode = this.createNode(key, value);
710
+ if (!this.isRealNode(newNode)) return undefined;
711
+ this._attachNewNode(hint, 'right', newNode);
712
+ if (this._isMapMode) this._store.set(key, newNode);
713
+ this._size++;
714
+ // Maintain header/min/max caches.
715
+ const NIL = this.NIL;
716
+ const hMin = this._header._left ?? NIL;
717
+ if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
718
+ const hMax = this._header._right ?? NIL;
719
+ if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
720
+ return newNode;
721
+ }
722
+
723
+ const succ = this._successorOf(hint);
724
+ if (succ && cmp(succ.key, key) <= 0) {
725
+ return this._setKVNode(key, value)?.node;
726
+ }
727
+
728
+ if (succ && !this.isRealNode(succ.left)) {
729
+ const newNode = this.createNode(key, value);
730
+ if (!this.isRealNode(newNode)) return undefined;
731
+ this._attachNewNode(succ, 'left', newNode);
732
+ if (this._isMapMode) this._store.set(key, newNode);
733
+ this._size++;
734
+ // Maintain header/min/max caches.
735
+ const NIL = this.NIL;
736
+ const hMin = this._header._left ?? NIL;
737
+ if (hMin === NIL || this._compare(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
738
+ const hMax = this._header._right ?? NIL;
739
+ if (hMax === NIL || this._compare(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
740
+ return newNode;
741
+ }
742
+
743
+ return this._setKVNode(key, value)?.node;
744
+ }
745
+
746
+ /**
747
+ * Boolean wrapper for setWithHintNode.
748
+ * @remarks Time O(log n) average, Space O(1)
749
+ */
750
+ setWithHint(key: K, value: V, hint?: RedBlackTreeNode<K, V>): boolean {
751
+ return this.setWithHintNode(key, value, hint) !== undefined;
752
+ }
753
+
754
+ /**
755
+ * Insert or update a key/value (map mode) or key-only (set mode).
756
+ *
757
+ * This method is optimized for:
758
+ * - monotonic inserts via min/max boundary fast paths
759
+ * - updates via a single-pass search (no double walk)
760
+ *
761
+ * @remarks Time O(log n) average, Space O(1)
352
762
  */
353
763
  override set(
354
764
  keyNodeOrEntry: K | RedBlackTreeNode<K, V> | [K | null | undefined, V | undefined] | null | undefined,
355
765
  value?: V
356
766
  ): boolean {
767
+ // Common path: tree.set(key, value) or tree.set([key, value]).
768
+ if (!this.isNode(keyNodeOrEntry)) {
769
+ if (keyNodeOrEntry === null || keyNodeOrEntry === undefined) return false;
770
+
771
+ if (this.isEntry(keyNodeOrEntry)) {
772
+ const key = keyNodeOrEntry[0];
773
+ if (key === null || key === undefined) return false;
774
+ const nextValue = value ?? keyNodeOrEntry[1];
775
+ return this._setKV(key, nextValue);
776
+ }
777
+
778
+ // key-only
779
+ return this._setKV(keyNodeOrEntry, value);
780
+ }
781
+
782
+ // Node insertion path (advanced usage)
357
783
  const [newNode, newValue] = this._keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value);
358
784
  if (!this.isRealNode(newNode)) return false;
359
785
 
@@ -365,12 +791,24 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
365
791
  } else {
366
792
  return false;
367
793
  }
368
- if (this._isMapMode) this._setValue(newNode.key, newValue);
794
+ if (this._isMapMode) {
795
+ const n = this.getNode(newNode.key);
796
+ if (this.isRealNode(n)) {
797
+ n.value = newValue as V;
798
+ this._store.set(n.key, n);
799
+ }
800
+ }
369
801
  this._size++;
370
802
  return true;
371
803
  }
372
804
  if (insertStatus === 'UPDATED') {
373
- if (this._isMapMode) this._setValue(newNode.key, newValue);
805
+ if (this._isMapMode) {
806
+ const n = this.getNode(newNode.key);
807
+ if (this.isRealNode(n)) {
808
+ n.value = newValue as V;
809
+ this._store.set(n.key, n);
810
+ }
811
+ }
374
812
  return true;
375
813
  }
376
814
  return false;
@@ -378,25 +816,30 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
378
816
 
379
817
  /**
380
818
  * 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.
819
+ * @remarks Time O(log n) average, Space O(1)
820
+ * @param keyNodeEntryRawOrPredicate - Key, node, or [key, value] entry identifying the node to delete.
383
821
  * @returns Array with deletion metadata (removed node, rebalancing hint if any).
384
822
  */
385
-
386
823
  override delete(
387
- keyNodeOrEntry: K | RedBlackTreeNode<K, V> | [K | null | undefined, V | undefined] | null | undefined
824
+ keyNodeEntryRawOrPredicate: BTNRep<K, V, RedBlackTreeNode<K, V>> | NodePredicate<RedBlackTreeNode<K, V> | null>
388
825
  ): BinaryTreeDeleteResult<RedBlackTreeNode<K, V>>[] {
389
- if (keyNodeOrEntry === null) return [];
826
+ if (keyNodeEntryRawOrPredicate === null) return [];
390
827
 
391
828
  const results: BinaryTreeDeleteResult<RedBlackTreeNode<K, V>>[] = [];
392
829
  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);
830
+ if (this._isPredicate(keyNodeEntryRawOrPredicate)) nodeToDelete = this.getNode(keyNodeEntryRawOrPredicate);
831
+ else nodeToDelete = this.isRealNode(keyNodeEntryRawOrPredicate) ? keyNodeEntryRawOrPredicate : this.getNode(keyNodeEntryRawOrPredicate);
395
832
 
396
833
  if (!nodeToDelete) {
397
834
  return results;
398
835
  }
399
836
 
837
+ // Track min/max cache updates before structural modifications.
838
+ const willDeleteMin = nodeToDelete === this._minNode;
839
+ const willDeleteMax = nodeToDelete === this._maxNode;
840
+ const nextMin = willDeleteMin ? this._successorOf(nodeToDelete) : undefined;
841
+ const nextMax = willDeleteMax ? this._predecessorOf(nodeToDelete) : undefined;
842
+
400
843
  let originalColor = nodeToDelete.color;
401
844
  let replacementNode: RedBlackTreeNode<K, V> | undefined;
402
845
 
@@ -439,6 +882,22 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
439
882
  if (this._isMapMode) this._store.delete(nodeToDelete.key);
440
883
  this._size--;
441
884
 
885
+ // Update min/max caches.
886
+ if (this._size <= 0) {
887
+ this._setMinCache(undefined);
888
+ this._setMaxCache(undefined);
889
+ } else {
890
+ if (willDeleteMin) this._setMinCache(nextMin);
891
+ if (willDeleteMax) this._setMaxCache(nextMax);
892
+ // Fallback if successor/predecessor was unavailable.
893
+ if (!this._minNode || !this.isRealNode(this._minNode)) {
894
+ this._setMinCache(this.isRealNode(this._root) ? this.getLeftMost(n => n, this._root) : undefined);
895
+ }
896
+ if (!this._maxNode || !this.isRealNode(this._maxNode)) {
897
+ this._setMaxCache(this.isRealNode(this._root) ? this.getRightMost(n => n, this._root) : undefined);
898
+ }
899
+ }
900
+
442
901
  if (originalColor === 'BLACK') {
443
902
  this._deleteFixup(replacementNode);
444
903
  }
@@ -450,7 +909,7 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
450
909
 
451
910
  /**
452
911
  * Transform entries into a like-kind red-black tree with possibly different key/value types.
453
- * @remarks Time O(n), Space O(n)
912
+ * @remarks Time O(n) average, Space O(n)
454
913
  * @template MK
455
914
  * @template MV
456
915
  * @template MR
@@ -459,7 +918,6 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
459
918
  * @param [thisArg] - See parameter type for details.
460
919
  * @returns A new RedBlackTree with mapped entries.
461
920
  */
462
-
463
921
  override map<MK = K, MV = V, MR = any>(
464
922
  callback: EntryCallback<K, V | undefined, [MK, MV]>,
465
923
  options?: Partial<RedBlackTreeOptions<MK, MV, MR>>,
@@ -474,6 +932,10 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
474
932
  return out;
475
933
  }
476
934
 
935
+ /**
936
+ * (Internal) Create an empty instance of the same concrete tree type.
937
+ * @remarks Time O(1) average, Space O(1)
938
+ */
477
939
  protected override _createInstance<TK = K, TV = V, TR = R>(options?: Partial<RedBlackTreeOptions<TK, TV, TR>>): this {
478
940
  const Ctor = this.constructor as unknown as new (
479
941
  iter?: Iterable<TK | RedBlackTreeNode<TK, TV> | [TK | null | undefined, TV | undefined] | null | undefined | TR>,
@@ -482,6 +944,10 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
482
944
  return new Ctor([], { ...this._snapshotOptions<TK, TV, TR>(), ...(options ?? {}) }) as unknown as this;
483
945
  }
484
946
 
947
+ /**
948
+ * (Internal) Create a like-kind tree (same concrete class) populated from an iterable.
949
+ * @remarks Time O(m log m) average (m = iterable length), Space O(m)
950
+ */
485
951
  protected override _createLike<TK = K, TV = V, TR = R>(
486
952
  iter: Iterable<
487
953
  TK | RedBlackTreeNode<TK, TV> | [TK | null | undefined, TV | undefined] | null | undefined | TR
@@ -495,13 +961,25 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
495
961
  return new Ctor(iter, { ...this._snapshotOptions<TK, TV, TR>(), ...(options ?? {}) });
496
962
  }
497
963
 
964
+ /**
965
+ * (Internal) Set the root pointer and keep header.parent in sync.
966
+ * @remarks Time O(1), Space O(1)
967
+ */
498
968
  protected override _setRoot(v: RedBlackTreeNode<K, V> | undefined) {
969
+ const NIL = this.NIL;
499
970
  if (v) {
500
971
  v.parent = undefined;
501
972
  }
502
973
  this._root = v;
974
+ // Keep header root pointer in sync even for internal operations (rotations/transplants)
975
+ // and for subclasses that may bypass _setKVNode.
976
+ this._header.parent = v ?? NIL;
503
977
  }
504
978
 
979
+ /**
980
+ * (Internal) Replace a node in place while preserving its color.
981
+ * @remarks Time O(1) average, Space O(1)
982
+ */
505
983
  protected override _replaceNode(
506
984
  oldNode: RedBlackTreeNode<K, V>,
507
985
  newNode: RedBlackTreeNode<K, V>
@@ -513,22 +991,25 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
513
991
 
514
992
  /**
515
993
  * (Protected) Standard BST insert followed by red-black fix-up.
516
- * @remarks Time O(log n), Space O(1)
994
+ * @remarks Time O(log n) average, Space O(1)
517
995
  * @param node - Node to insert.
518
996
  * @returns Status string: 'CREATED' or 'UPDATED'.
519
997
  */
520
-
521
998
  protected _insert(node: RedBlackTreeNode<K, V>): CRUD {
522
- let current = this.root ?? this.NIL;
523
- let parent: RedBlackTreeNode<K, V> | undefined = undefined;
999
+ const NIL = this.NIL;
1000
+ const cmp = this._compare.bind(this);
524
1001
 
525
- while (current !== this.NIL) {
1002
+ let current = this._header.parent ?? NIL;
1003
+ let parent: RedBlackTreeNode<K, V> | undefined;
1004
+ let lastCompared = 0;
1005
+
1006
+ while (current !== NIL) {
526
1007
  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;
1008
+ lastCompared = cmp(node.key, current.key);
1009
+ if (lastCompared < 0) {
1010
+ current = current.left ?? NIL;
1011
+ } else if (lastCompared > 0) {
1012
+ current = current.right ?? NIL;
532
1013
  } else {
533
1014
  this._replaceNode(current, node);
534
1015
  return 'UPDATED';
@@ -539,14 +1020,14 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
539
1020
 
540
1021
  if (!parent) {
541
1022
  this._setRoot(node);
542
- } else if (this._compare(node.key, parent.key) < 0) {
1023
+ } else if (lastCompared < 0) {
543
1024
  parent.left = node;
544
1025
  } else {
545
1026
  parent.right = node;
546
1027
  }
547
1028
 
548
- node.left = this.NIL;
549
- node.right = this.NIL;
1029
+ node.left = NIL;
1030
+ node.right = NIL;
550
1031
  node.color = 'RED';
551
1032
 
552
1033
  this._insertFixup(node);
@@ -560,7 +1041,6 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
560
1041
  * @param v - Replacement subtree root (may be undefined).
561
1042
  * @returns void
562
1043
  */
563
-
564
1044
  protected _transplant(u: RedBlackTreeNode<K, V>, v: RedBlackTreeNode<K, V> | undefined): void {
565
1045
  if (!u.parent) {
566
1046
  this._setRoot(v);
@@ -577,53 +1057,68 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
577
1057
 
578
1058
  /**
579
1059
  * (Protected) Restore red-black properties after insertion (recolor/rotate).
580
- * @remarks Time O(log n), Space O(1)
1060
+ * @remarks Time O(log n) average, Space O(1)
581
1061
  * @param z - Recently inserted node.
582
1062
  * @returns void
583
1063
  */
584
-
585
1064
  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;
1065
+ const leftRotate = this._leftRotate.bind(this);
1066
+ const rightRotate = this._rightRotate.bind(this);
1067
+
1068
+ while (z) {
1069
+ const p = z.parent;
1070
+ if (!p || p.color !== 'RED') break;
1071
+
1072
+ const gp = p.parent;
1073
+ if (!gp) break;
1074
+
1075
+ if (p === gp.left) {
1076
+ const y = gp.right;
589
1077
  if (y?.color === 'RED') {
590
- z.parent.color = 'BLACK';
1078
+ p.color = 'BLACK';
591
1079
  y.color = 'BLACK';
592
- z.parent.parent.color = 'RED';
1080
+ gp.color = 'RED';
1081
+ z = gp;
1082
+ continue;
1083
+ }
593
1084
 
594
- z = z.parent.parent;
595
- } else {
596
- if (z === z.parent.right) {
597
- z = z.parent;
598
- this._leftRotate(z);
599
- }
1085
+ if (z === p.right) {
1086
+ z = p;
1087
+ leftRotate(z);
1088
+ }
600
1089
 
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
- }
1090
+ const p2 = z?.parent;
1091
+ const gp2 = p2?.parent;
1092
+ if (p2 && gp2) {
1093
+ p2.color = 'BLACK';
1094
+ gp2.color = 'RED';
1095
+ rightRotate(gp2);
606
1096
  }
607
1097
  } else {
608
- const y: RedBlackTreeNode<K, V> | undefined = z?.parent?.parent?.left ?? undefined;
1098
+ const y = gp.left;
609
1099
  if (y?.color === 'RED') {
610
- z.parent.color = 'BLACK';
1100
+ p.color = 'BLACK';
611
1101
  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
- }
1102
+ gp.color = 'RED';
1103
+ z = gp;
1104
+ continue;
1105
+ }
619
1106
 
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
- }
1107
+ if (z === p.left) {
1108
+ z = p;
1109
+ rightRotate(z);
1110
+ }
1111
+
1112
+ const p2 = z?.parent;
1113
+ const gp2 = p2?.parent;
1114
+ if (p2 && gp2) {
1115
+ p2.color = 'BLACK';
1116
+ gp2.color = 'RED';
1117
+ leftRotate(gp2);
625
1118
  }
626
1119
  }
1120
+
1121
+ break;
627
1122
  }
628
1123
 
629
1124
  if (this.isRealNode(this._root)) this._root.color = 'BLACK';
@@ -631,11 +1126,10 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
631
1126
 
632
1127
  /**
633
1128
  * (Protected) Restore red-black properties after deletion (recolor/rotate).
634
- * @remarks Time O(log n), Space O(1)
1129
+ * @remarks Time O(log n) average, Space O(1)
635
1130
  * @param node - Child that replaced the deleted node (may be undefined).
636
1131
  * @returns void
637
1132
  */
638
-
639
1133
  protected _deleteFixup(node: RedBlackTreeNode<K, V> | undefined): void {
640
1134
  if (!node || node === this.root || node.color === 'BLACK') {
641
1135
  if (node) {
@@ -705,7 +1199,6 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
705
1199
  * @param x - Pivot node to rotate around.
706
1200
  * @returns void
707
1201
  */
708
-
709
1202
  protected _leftRotate(x: RedBlackTreeNode<K, V> | undefined): void {
710
1203
  if (!x || !x.right) {
711
1204
  return;
@@ -738,7 +1231,6 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
738
1231
  * @param y - Pivot node to rotate around.
739
1232
  * @returns void
740
1233
  */
741
-
742
1234
  protected _rightRotate(y: RedBlackTreeNode<K, V> | undefined): void {
743
1235
  if (!y || !y.left) {
744
1236
  return;