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.
- package/.github/workflows/ci.yml +9 -0
- package/CHANGELOG.md +1 -1
- package/README.md +14 -3
- package/README_CN.md +119 -275
- package/benchmark/report.html +1 -1
- package/benchmark/report.json +20 -324
- package/dist/cjs/index.cjs +689 -182
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs-legacy/index.cjs +693 -185
- package/dist/cjs-legacy/index.cjs.map +1 -1
- package/dist/esm/index.mjs +689 -182
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm-legacy/index.mjs +693 -185
- package/dist/esm-legacy/index.mjs.map +1 -1
- package/dist/leetcode/avl-tree-counter.mjs +2957 -0
- package/dist/leetcode/avl-tree-multi-map.mjs +2889 -0
- package/dist/leetcode/avl-tree.mjs +2720 -0
- package/dist/leetcode/binary-tree.mjs +1594 -0
- package/dist/leetcode/bst.mjs +2398 -0
- package/dist/leetcode/deque.mjs +683 -0
- package/dist/leetcode/directed-graph.mjs +1733 -0
- package/dist/leetcode/doubly-linked-list.mjs +709 -0
- package/dist/leetcode/hash-map.mjs +493 -0
- package/dist/leetcode/heap.mjs +542 -0
- package/dist/leetcode/max-heap.mjs +375 -0
- package/dist/leetcode/max-priority-queue.mjs +383 -0
- package/dist/leetcode/min-heap.mjs +363 -0
- package/dist/leetcode/min-priority-queue.mjs +371 -0
- package/dist/leetcode/priority-queue.mjs +363 -0
- package/dist/leetcode/queue.mjs +943 -0
- package/dist/leetcode/red-black-tree.mjs +2765 -0
- package/dist/leetcode/singly-linked-list.mjs +754 -0
- package/dist/leetcode/stack.mjs +217 -0
- package/dist/leetcode/tree-counter.mjs +3039 -0
- package/dist/leetcode/tree-multi-map.mjs +2913 -0
- package/dist/leetcode/trie.mjs +413 -0
- package/dist/leetcode/undirected-graph.mjs +1650 -0
- package/dist/types/data-structures/base/linear-base.d.ts +6 -6
- package/dist/types/data-structures/binary-tree/avl-tree-counter.d.ts +1 -1
- package/dist/types/data-structures/binary-tree/avl-tree-multi-map.d.ts +2 -2
- package/dist/types/data-structures/binary-tree/avl-tree.d.ts +10 -10
- package/dist/types/data-structures/binary-tree/binary-tree.d.ts +25 -27
- package/dist/types/data-structures/binary-tree/bst.d.ts +13 -12
- package/dist/types/data-structures/binary-tree/red-black-tree.d.ts +151 -21
- package/dist/types/data-structures/binary-tree/tree-counter.d.ts +4 -4
- package/dist/types/data-structures/binary-tree/tree-multi-map.d.ts +2 -2
- package/dist/types/interfaces/binary-tree.d.ts +1 -1
- package/dist/umd/data-structure-typed.js +689 -181
- package/dist/umd/data-structure-typed.js.map +1 -1
- package/dist/umd/data-structure-typed.min.js +3 -3
- package/dist/umd/data-structure-typed.min.js.map +1 -1
- package/package.json +50 -172
- package/src/data-structures/base/linear-base.ts +2 -12
- package/src/data-structures/binary-tree/avl-tree-counter.ts +6 -6
- package/src/data-structures/binary-tree/avl-tree-multi-map.ts +13 -13
- package/src/data-structures/binary-tree/avl-tree.ts +15 -15
- package/src/data-structures/binary-tree/binary-tree.ts +57 -60
- package/src/data-structures/binary-tree/bst.ts +100 -26
- package/src/data-structures/binary-tree/red-black-tree.ts +586 -76
- package/src/data-structures/binary-tree/tree-counter.ts +25 -13
- package/src/data-structures/binary-tree/tree-multi-map.ts +13 -13
- package/src/data-structures/queue/deque.ts +10 -0
- package/src/interfaces/binary-tree.ts +1 -1
- package/test/performance/data-structures/binary-tree/red-black-tree.test.ts +1 -2
- package/test/unit/data-structures/base/iterable-element-base.coverage.test.ts +106 -0
- package/test/unit/data-structures/base/iterable-element-base.more-branches.coverage.test.ts +61 -0
- package/test/unit/data-structures/base/linear-base.array.coverage.test.ts +168 -0
- package/test/unit/data-structures/base/linear-base.concat-else.coverage.test.ts +82 -0
- package/test/unit/data-structures/base/linear-base.coverage.test.ts +72 -0
- package/test/unit/data-structures/base/linear-base.more-branches.coverage.test.ts +417 -0
- package/test/unit/data-structures/binary-tree/avl-tree-counter.more-branches-3.coverage.test.ts +146 -0
- package/test/unit/data-structures/binary-tree/avl-tree-counter.more-branches.coverage.test.ts +93 -0
- package/test/unit/data-structures/binary-tree/avl-tree-counter.test.ts +30 -30
- package/test/unit/data-structures/binary-tree/avl-tree-multi-map.coverage.test.ts +108 -0
- package/test/unit/data-structures/binary-tree/avl-tree-multi-map.more-branches-2.coverage.test.ts +85 -0
- package/test/unit/data-structures/binary-tree/avl-tree-multi-map.test.ts +46 -46
- package/test/unit/data-structures/binary-tree/avl-tree-node.familyPosition-root-left.coverage.test.ts +17 -0
- package/test/unit/data-structures/binary-tree/avl-tree.more-branches-2.coverage.test.ts +99 -0
- package/test/unit/data-structures/binary-tree/avl-tree.test.ts +43 -43
- package/test/unit/data-structures/binary-tree/binary-indexed-tree.more-branches.coverage.test.ts +18 -0
- package/test/unit/data-structures/binary-tree/binary-tree.more-branches.coverage.test.ts +56 -0
- package/test/unit/data-structures/binary-tree/binary-tree.remaining-branches.coverage.test.ts +229 -0
- package/test/unit/data-structures/binary-tree/binary-tree.test.ts +151 -151
- package/test/unit/data-structures/binary-tree/bst.bound-by-predicate.coverage.test.ts +33 -0
- package/test/unit/data-structures/binary-tree/bst.coverage.test.ts +94 -0
- package/test/unit/data-structures/binary-tree/bst.deletebykey.coverage.test.ts +70 -0
- package/test/unit/data-structures/binary-tree/bst.deletewhere.coverage.test.ts +37 -0
- package/test/unit/data-structures/binary-tree/bst.floor-lower-predicate.coverage.test.ts +29 -0
- package/test/unit/data-structures/binary-tree/bst.floor-setmany.coverage.test.ts +72 -0
- package/test/unit/data-structures/binary-tree/bst.getnode.range-ensure.coverage.test.ts +22 -0
- package/test/unit/data-structures/binary-tree/bst.misc-branches.coverage.test.ts +100 -0
- package/test/unit/data-structures/binary-tree/bst.more-branches-2.coverage.test.ts +133 -0
- package/test/unit/data-structures/binary-tree/bst.more-branches-3.coverage.test.ts +45 -0
- package/test/unit/data-structures/binary-tree/bst.more-branches-4.coverage.test.ts +36 -0
- package/test/unit/data-structures/binary-tree/bst.more-branches-5.coverage.test.ts +40 -0
- package/test/unit/data-structures/binary-tree/bst.more.coverage.test.ts +39 -0
- package/test/unit/data-structures/binary-tree/bst.node-family.coverage.test.ts +29 -0
- package/test/unit/data-structures/binary-tree/bst.range-pruning.coverage.test.ts +43 -0
- package/test/unit/data-structures/binary-tree/bst.search-fastpath.coverage.test.ts +30 -0
- package/test/unit/data-structures/binary-tree/bst.test.ts +124 -154
- package/test/unit/data-structures/binary-tree/overall.test.ts +20 -20
- package/test/unit/data-structures/binary-tree/red-black-tree.boundary-corruption-repair.coverage.test.ts +66 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.boundary-max-update.coverage.test.ts +18 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.boundary-null.coverage.test.ts +53 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.boundary-stale-cache.coverage.test.ts +25 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.boundary-update.coverage.test.ts +23 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.cache-delete.coverage.test.ts +49 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.cache-edge.coverage.test.ts +37 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.cache-stale-insert.coverage.test.ts +39 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.coverage.test.ts +334 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.delete-fixup.coverage.test.ts +68 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.delete-successor.coverage.test.ts +75 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.factories.coverage.test.ts +26 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.hint-cache-compare-update.coverage.test.ts +74 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.hint-cache-no-update.coverage.test.ts +44 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.hint-cache-nullish.coverage.test.ts +61 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.hint-mapmode-defined.coverage.test.ts +35 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.hint-mapmode-undefined.coverage.test.ts +43 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.hint-more.coverage.test.ts +99 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.hint.coverage.test.ts +60 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.insert-cache-nullish.coverage.test.ts +29 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.insert-header-parent-nullish.coverage.test.ts +17 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.internal-walk.coverage.test.ts +57 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.minmax-cache.test.ts +65 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.misc-inputs.coverage.test.ts +17 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.more-branches-2.coverage.test.ts +121 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.more-branches-3.coverage.test.ts +55 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.more-branches-4.coverage.test.ts +44 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.predsucc.coverage.test.ts +40 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.remaining-branches.coverage.test.ts +123 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.set-inputs.coverage.test.ts +64 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.setkvnode-parent-cache.coverage.test.ts +79 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.setkvnode-remaining.coverage.test.ts +44 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.setkvnode-uncovered.coverage.test.ts +74 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.test.ts +141 -141
- package/test/unit/data-structures/binary-tree/red-black-tree.update-branches.coverage.test.ts +30 -0
- package/test/unit/data-structures/binary-tree/segment-tree.more-branches.coverage.test.ts +31 -0
- package/test/unit/data-structures/binary-tree/tree-counter.coverage.test.ts +115 -0
- package/test/unit/data-structures/binary-tree/tree-counter.more-branches.coverage.test.ts +244 -0
- package/test/unit/data-structures/binary-tree/tree-counter.test.ts +41 -39
- package/test/unit/data-structures/binary-tree/tree-multi-map.coverage.test.ts +104 -0
- package/test/unit/data-structures/binary-tree/tree-multi-map.more-branches-2.coverage.test.ts +59 -0
- package/test/unit/data-structures/binary-tree/tree-multi-map.test.ts +145 -145
- package/test/unit/data-structures/graph/abstract-graph.more-branches-2.coverage.test.ts +40 -0
- package/test/unit/data-structures/graph/abstract-graph.more-branches-3.coverage.test.ts +65 -0
- package/test/unit/data-structures/graph/abstract-graph.more-branches-4.coverage.test.ts +98 -0
- package/test/unit/data-structures/graph/abstract-graph.more-branches-5.coverage.test.ts +51 -0
- package/test/unit/data-structures/graph/abstract-graph.more-branches.coverage.test.ts +62 -0
- package/test/unit/data-structures/graph/directed-graph.more-branches-2.coverage.test.ts +38 -0
- package/test/unit/data-structures/graph/directed-graph.more-branches-3.coverage.test.ts +25 -0
- package/test/unit/data-structures/graph/directed-graph.more-branches.coverage.test.ts +82 -0
- package/test/unit/data-structures/graph/map-graph.more-branches.coverage.test.ts +22 -0
- package/test/unit/data-structures/graph/undirected-graph.more-branches-2.coverage.test.ts +35 -0
- package/test/unit/data-structures/graph/undirected-graph.more-branches.coverage.test.ts +87 -0
- package/test/unit/data-structures/hash/hash-map.more-branches.coverage.test.ts +64 -0
- package/test/unit/data-structures/hash/hash-map.toEntryFn-branch.coverage.test.ts +9 -0
- package/test/unit/data-structures/heap/heap.misc-branches.coverage.test.ts +110 -0
- package/test/unit/data-structures/heap/heap.remaining-branches.coverage.test.ts +22 -0
- package/test/unit/data-structures/heap/max-heap.coverage.test.ts +29 -0
- package/test/unit/data-structures/linked-list/doubly-linked-list.more-branches.coverage.test.ts +72 -0
- package/test/unit/data-structures/linked-list/linked-list.unshiftMany-else.coverage.test.ts +15 -0
- package/test/unit/data-structures/linked-list/singly-linked-list.coverage.test.ts +221 -0
- package/test/unit/data-structures/linked-list/singly-linked-list.more-branches.coverage.test.ts +86 -0
- package/test/unit/data-structures/linked-list/skip-linked-list.more-branches.coverage.test.ts +31 -0
- package/test/unit/data-structures/matrix/matrix.more-branches.coverage.test.ts +81 -0
- package/test/unit/data-structures/matrix/matrix.pivotElement-nullish.coverage.test.ts +28 -0
- package/test/unit/data-structures/priority-queue/max-priority-queue.more-branches.coverage.test.ts +10 -0
- package/test/unit/data-structures/priority-queue/priority-queue.coverage.test.ts +21 -0
- package/test/unit/data-structures/queue/deque.coverage.test.ts +173 -0
- package/test/unit/data-structures/queue/deque.more-branches-2.coverage.test.ts +39 -0
- package/test/unit/data-structures/queue/deque.more-branches-3.coverage.test.ts +9 -0
- package/test/unit/data-structures/queue/deque.more-branches.coverage.test.ts +95 -0
- package/test/unit/data-structures/queue/queue.coverage.test.ts +138 -0
- package/test/unit/data-structures/queue/queue.more-branches-2.coverage.test.ts +27 -0
- package/test/unit/data-structures/stack/stack.coverage.test.ts +112 -0
- package/test/unit/data-structures/tree/tree.more-branches.coverage.test.ts +9 -0
- package/test/unit/data-structures/trie/trie.more-branches-2.coverage.test.ts +51 -0
- package/test/utils/patch.ts +33 -0
- package/tsup.config.js +50 -21
- package/tsup.umd.config.js +29 -0
- 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
|
|
28
|
-
* @remarks Time O(
|
|
29
|
-
* @param key -
|
|
30
|
-
* @param [value]-
|
|
31
|
-
* @param color -
|
|
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
|
|
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.
|
|
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
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
842
|
+
keyNodeEntryRawOrPredicate: BTNRep<K, V, RedBlackTreeNode<K, V>> | NodePredicate<RedBlackTreeNode<K, V> | null>
|
|
388
843
|
): BinaryTreeDeleteResult<RedBlackTreeNode<K, V>>[] {
|
|
389
|
-
if (
|
|
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(
|
|
394
|
-
else nodeToDelete = this.isRealNode(
|
|
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.
|
|
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
|
-
|
|
523
|
-
|
|
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 !==
|
|
1024
|
+
while (current !== NIL) {
|
|
526
1025
|
parent = current;
|
|
527
|
-
|
|
528
|
-
if (
|
|
529
|
-
current = current.left ??
|
|
530
|
-
} else if (
|
|
531
|
-
current = current.right ??
|
|
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 (
|
|
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 =
|
|
549
|
-
node.right =
|
|
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
|
-
|
|
587
|
-
|
|
588
|
-
|
|
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
|
-
|
|
1096
|
+
p.color = 'BLACK';
|
|
591
1097
|
y.color = 'BLACK';
|
|
592
|
-
|
|
1098
|
+
gp.color = 'RED';
|
|
1099
|
+
z = gp;
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
593
1102
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
this._leftRotate(z);
|
|
599
|
-
}
|
|
1103
|
+
if (z === p.right) {
|
|
1104
|
+
z = p;
|
|
1105
|
+
leftRotate(z);
|
|
1106
|
+
}
|
|
600
1107
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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
|
|
1116
|
+
const y = gp.left;
|
|
609
1117
|
if (y?.color === 'RED') {
|
|
610
|
-
|
|
1118
|
+
p.color = 'BLACK';
|
|
611
1119
|
y.color = 'BLACK';
|
|
612
|
-
|
|
613
|
-
z =
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
z = z.parent;
|
|
617
|
-
this._rightRotate(z);
|
|
618
|
-
}
|
|
1120
|
+
gp.color = 'RED';
|
|
1121
|
+
z = gp;
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
619
1124
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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;
|