data-structure-typed 2.4.4 → 2.5.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 (80) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/README.md +34 -1
  3. package/dist/cjs/index.cjs +10639 -2151
  4. package/dist/cjs-legacy/index.cjs +10694 -2195
  5. package/dist/esm/index.mjs +10639 -2150
  6. package/dist/esm-legacy/index.mjs +10694 -2194
  7. package/dist/types/common/error.d.ts +23 -0
  8. package/dist/types/common/index.d.ts +1 -0
  9. package/dist/types/data-structures/base/iterable-element-base.d.ts +1 -1
  10. package/dist/types/data-structures/binary-tree/avl-tree.d.ts +128 -51
  11. package/dist/types/data-structures/binary-tree/binary-indexed-tree.d.ts +210 -164
  12. package/dist/types/data-structures/binary-tree/binary-tree.d.ts +439 -78
  13. package/dist/types/data-structures/binary-tree/bst.d.ts +311 -28
  14. package/dist/types/data-structures/binary-tree/red-black-tree.d.ts +217 -31
  15. package/dist/types/data-structures/binary-tree/segment-tree.d.ts +218 -152
  16. package/dist/types/data-structures/binary-tree/tree-map.d.ts +1281 -5
  17. package/dist/types/data-structures/binary-tree/tree-multi-map.d.ts +1087 -201
  18. package/dist/types/data-structures/binary-tree/tree-multi-set.d.ts +858 -65
  19. package/dist/types/data-structures/binary-tree/tree-set.d.ts +1133 -5
  20. package/dist/types/data-structures/graph/abstract-graph.d.ts +44 -0
  21. package/dist/types/data-structures/graph/directed-graph.d.ts +220 -47
  22. package/dist/types/data-structures/graph/map-graph.d.ts +59 -1
  23. package/dist/types/data-structures/graph/undirected-graph.d.ts +218 -59
  24. package/dist/types/data-structures/hash/hash-map.d.ts +230 -77
  25. package/dist/types/data-structures/heap/heap.d.ts +287 -99
  26. package/dist/types/data-structures/heap/max-heap.d.ts +46 -0
  27. package/dist/types/data-structures/heap/min-heap.d.ts +59 -0
  28. package/dist/types/data-structures/linked-list/doubly-linked-list.d.ts +286 -44
  29. package/dist/types/data-structures/linked-list/singly-linked-list.d.ts +278 -65
  30. package/dist/types/data-structures/linked-list/skip-linked-list.d.ts +415 -12
  31. package/dist/types/data-structures/matrix/matrix.d.ts +331 -0
  32. package/dist/types/data-structures/priority-queue/max-priority-queue.d.ts +57 -0
  33. package/dist/types/data-structures/priority-queue/min-priority-queue.d.ts +60 -0
  34. package/dist/types/data-structures/priority-queue/priority-queue.d.ts +60 -0
  35. package/dist/types/data-structures/queue/deque.d.ts +313 -66
  36. package/dist/types/data-structures/queue/queue.d.ts +211 -42
  37. package/dist/types/data-structures/stack/stack.d.ts +174 -32
  38. package/dist/types/data-structures/trie/trie.d.ts +213 -43
  39. package/dist/types/types/data-structures/binary-tree/segment-tree.d.ts +1 -1
  40. package/dist/types/types/data-structures/linked-list/skip-linked-list.d.ts +1 -4
  41. package/dist/types/types/data-structures/queue/deque.d.ts +6 -0
  42. package/dist/umd/data-structure-typed.js +10725 -2221
  43. package/dist/umd/data-structure-typed.min.js +4 -2
  44. package/package.json +5 -4
  45. package/src/common/error.ts +60 -0
  46. package/src/common/index.ts +2 -0
  47. package/src/data-structures/base/iterable-element-base.ts +2 -2
  48. package/src/data-structures/binary-tree/avl-tree.ts +146 -51
  49. package/src/data-structures/binary-tree/binary-indexed-tree.ts +317 -247
  50. package/src/data-structures/binary-tree/binary-tree.ts +567 -121
  51. package/src/data-structures/binary-tree/bst.ts +370 -37
  52. package/src/data-structures/binary-tree/red-black-tree.ts +328 -96
  53. package/src/data-structures/binary-tree/segment-tree.ts +378 -248
  54. package/src/data-structures/binary-tree/tree-map.ts +1411 -13
  55. package/src/data-structures/binary-tree/tree-multi-map.ts +1218 -215
  56. package/src/data-structures/binary-tree/tree-multi-set.ts +959 -69
  57. package/src/data-structures/binary-tree/tree-set.ts +1257 -15
  58. package/src/data-structures/graph/abstract-graph.ts +106 -1
  59. package/src/data-structures/graph/directed-graph.ts +233 -47
  60. package/src/data-structures/graph/map-graph.ts +59 -1
  61. package/src/data-structures/graph/undirected-graph.ts +308 -59
  62. package/src/data-structures/hash/hash-map.ts +254 -79
  63. package/src/data-structures/heap/heap.ts +305 -102
  64. package/src/data-structures/heap/max-heap.ts +48 -3
  65. package/src/data-structures/heap/min-heap.ts +59 -0
  66. package/src/data-structures/linked-list/doubly-linked-list.ts +303 -44
  67. package/src/data-structures/linked-list/singly-linked-list.ts +293 -65
  68. package/src/data-structures/linked-list/skip-linked-list.ts +707 -90
  69. package/src/data-structures/matrix/matrix.ts +433 -22
  70. package/src/data-structures/priority-queue/max-priority-queue.ts +59 -3
  71. package/src/data-structures/priority-queue/min-priority-queue.ts +60 -0
  72. package/src/data-structures/priority-queue/priority-queue.ts +60 -0
  73. package/src/data-structures/queue/deque.ts +358 -68
  74. package/src/data-structures/queue/queue.ts +223 -42
  75. package/src/data-structures/stack/stack.ts +184 -32
  76. package/src/data-structures/trie/trie.ts +227 -44
  77. package/src/types/data-structures/binary-tree/segment-tree.ts +1 -1
  78. package/src/types/data-structures/linked-list/skip-linked-list.ts +2 -1
  79. package/src/types/data-structures/queue/deque.ts +7 -0
  80. package/src/utils/utils.ts +4 -2
@@ -13,6 +13,7 @@ import type {
13
13
  FamilyPosition, NodePredicate,
14
14
  OptNode,
15
15
  RBTNColor,
16
+ IterationType,
16
17
  RedBlackTreeOptions
17
18
  } from '../../types';
18
19
  import { BST } from './bst';
@@ -97,16 +98,12 @@ export class RedBlackTreeNode<K = any, V = any> {
97
98
  *
98
99
  * @returns The height.
99
100
  */
101
+ /* istanbul ignore next -- covered by AVLTree tests (subclass uses height) */
100
102
  get height(): number {
101
103
  return this._height;
102
104
  }
103
105
 
104
- /**
105
- * Sets the height of the node.
106
- * @remarks Time O(1), Space O(1)
107
- *
108
- * @param value - The new height.
109
- */
106
+ /* istanbul ignore next -- covered by AVLTree tests (subclass uses height) */
110
107
  set height(value: number) {
111
108
  this._height = value;
112
109
  }
@@ -141,20 +138,11 @@ export class RedBlackTreeNode<K = any, V = any> {
141
138
  *
142
139
  * @returns The subtree node count.
143
140
  */
141
+ /* istanbul ignore next -- internal field, exercised indirectly via tree operations */
144
142
  get count(): number {
145
143
  return this._count;
146
144
  }
147
145
 
148
- /**
149
- * Sets the count of nodes in the subtree.
150
- * @remarks Time O(1), Space O(1)
151
- *
152
- * @param value - The new count.
153
- */
154
- set count(value: number) {
155
- this._count = value;
156
- }
157
-
158
146
  /**
159
147
  * Gets the position of the node relative to its parent.
160
148
  * @remarks Time O(1), Space O(1)
@@ -172,6 +160,7 @@ export class RedBlackTreeNode<K = any, V = any> {
172
160
  return this.left || this.right ? 'ROOT_RIGHT' : 'RIGHT';
173
161
  }
174
162
 
163
+ /* istanbul ignore next -- defensive: unreachable if tree structure is correct */
175
164
  return 'MAL_NODE';
176
165
  }
177
166
  }
@@ -186,23 +175,6 @@ export class RedBlackTreeNode<K = any, V = any> {
186
175
  * 2. It is BST itself. Compared with Heap which is not completely ordered, RedBlackTree is completely ordered.
187
176
  *
188
177
  * @example
189
- * // basic Red-Black Tree with simple number keys
190
- * // Create a simple Red-Black Tree with numeric keys
191
- * const tree = new RedBlackTree([5, 2, 8, 1, 9]);
192
- *
193
- * tree.print();
194
- * // _2___
195
- * // / \
196
- * // 1 _8_
197
- * // / \
198
- * // 5 9
199
- *
200
- * // Verify the tree maintains sorted order
201
- * console.log([...tree.keys()]); // [1, 2, 5, 8, 9];
202
- *
203
- * // Check size
204
- * console.log(tree.size); // 5;
205
- * @example
206
178
  * // Red-Black Tree with key-value pairs for lookups
207
179
  * interface Employee {
208
180
  * id: number;
@@ -368,6 +340,50 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
368
340
  /**
369
341
  * Remove all nodes and clear internal caches.
370
342
  * @remarks Time O(n) average, Space O(1)
343
+
344
+
345
+
346
+
347
+
348
+
349
+
350
+
351
+
352
+
353
+
354
+
355
+
356
+
357
+
358
+
359
+
360
+
361
+
362
+
363
+
364
+
365
+
366
+
367
+
368
+
369
+
370
+
371
+
372
+
373
+
374
+
375
+
376
+
377
+
378
+
379
+
380
+
381
+
382
+ * @example
383
+ * // Remove all entries
384
+ * const rbt = new RedBlackTree<number>([1, 2, 3]);
385
+ * rbt.clear();
386
+ * console.log(rbt.isEmpty()); // true;
371
387
  */
372
388
  override clear() {
373
389
  super.clear();
@@ -603,10 +619,10 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
603
619
  if (hMin === NIL || hMax === NIL) {
604
620
  this._setMinCache(newNode);
605
621
  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);
622
+ } else /* istanbul ignore next -- boundary fast-paths at top of _setKVNode intercept boundary inserts before normal path */ if (parent === hMax && lastCompared > 0) {
623
+ /* istanbul ignore next */ this._setMaxCache(newNode);
624
+ } else /* istanbul ignore next */ if (parent === hMin && lastCompared < 0) {
625
+ /* istanbul ignore next */ this._setMinCache(newNode);
610
626
  } else {
611
627
  if (cmp(newNode.key, hMin.key) < 0) this._setMinCache(newNode);
612
628
  if (cmp(newNode.key, hMax.key) > 0) this._setMaxCache(newNode);
@@ -700,6 +716,7 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
700
716
  return newNode;
701
717
  }
702
718
 
719
+ /* istanbul ignore next -- structurally unreachable: predecessor never has a right child (it's the max of left subtree) */
703
720
  return this._setKVNode(key, value)?.node;
704
721
  }
705
722
 
@@ -740,6 +757,7 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
740
757
  return newNode;
741
758
  }
742
759
 
760
+ /* istanbul ignore next -- structurally unreachable: successor never has a left child (it's the min of right subtree) */
743
761
  return this._setKVNode(key, value)?.node;
744
762
  }
745
763
 
@@ -759,6 +777,63 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
759
777
  * - updates via a single-pass search (no double walk)
760
778
  *
761
779
  * @remarks Time O(log n) average, Space O(1)
780
+
781
+
782
+
783
+
784
+
785
+
786
+
787
+
788
+
789
+
790
+
791
+
792
+
793
+
794
+
795
+
796
+
797
+
798
+
799
+
800
+
801
+
802
+
803
+
804
+
805
+
806
+
807
+
808
+
809
+
810
+
811
+
812
+
813
+
814
+
815
+
816
+
817
+
818
+
819
+
820
+ * @example
821
+ * // basic Red-Black Tree with simple number keys
822
+ * // Create a simple Red-Black Tree with numeric keys
823
+ * const tree = new RedBlackTree([5, 2, 8, 1, 9]);
824
+ *
825
+ * tree.print();
826
+ * // _2___
827
+ * // / \
828
+ * // 1 _8_
829
+ * // / \
830
+ * // 5 9
831
+ *
832
+ * // Verify the tree maintains sorted order
833
+ * console.log([...tree.keys()]); // [1, 2, 5, 8, 9];
834
+ *
835
+ * // Check size
836
+ * console.log(tree.size); // 5;
762
837
  */
763
838
  override set(
764
839
  keyNodeOrEntry: K | RedBlackTreeNode<K, V> | [K | null | undefined, V | undefined] | null | undefined,
@@ -811,6 +886,7 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
811
886
  }
812
887
  return true;
813
888
  }
889
+ /* istanbul ignore next -- defensive: _insert only returns CREATED|UPDATED */
814
890
  return false;
815
891
  }
816
892
 
@@ -819,6 +895,54 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
819
895
  * @remarks Time O(log n) average, Space O(1)
820
896
  * @param keyNodeEntryRawOrPredicate - Key, node, or [key, value] entry identifying the node to delete.
821
897
  * @returns Array with deletion metadata (removed node, rebalancing hint if any).
898
+
899
+
900
+
901
+
902
+
903
+
904
+
905
+
906
+
907
+
908
+
909
+
910
+
911
+
912
+
913
+
914
+
915
+
916
+
917
+
918
+
919
+
920
+
921
+
922
+
923
+
924
+
925
+
926
+
927
+
928
+
929
+
930
+
931
+
932
+
933
+
934
+
935
+
936
+
937
+
938
+
939
+
940
+ * @example
941
+ * // Remove and rebalance
942
+ * const rbt = new RedBlackTree<number>([10, 5, 15, 3, 7]);
943
+ * rbt.delete(5);
944
+ * console.log(rbt.has(5)); // false;
945
+ * console.log(rbt.size); // 4;
822
946
  */
823
947
  override delete(
824
948
  keyNodeEntryRawOrPredicate: BTNRep<K, V, RedBlackTreeNode<K, V>> | NodePredicate<RedBlackTreeNode<K, V> | null>
@@ -841,39 +965,38 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
841
965
  const nextMax = willDeleteMax ? this._predecessorOf(nodeToDelete) : undefined;
842
966
 
843
967
  let originalColor = nodeToDelete.color;
844
- let replacementNode: RedBlackTreeNode<K, V> | undefined;
968
+ const NIL = this.NIL;
969
+ let replacementNode: RedBlackTreeNode<K, V> = NIL;
845
970
 
846
971
  if (!this.isRealNode(nodeToDelete.left)) {
847
- if (nodeToDelete.right !== null) {
848
- replacementNode = nodeToDelete.right;
849
- this._transplant(nodeToDelete, nodeToDelete.right);
850
- }
972
+ // No real left child → replace with right (may be NIL)
973
+ replacementNode = nodeToDelete.right ?? NIL;
974
+ this._transplant(nodeToDelete, replacementNode);
851
975
  } else if (!this.isRealNode(nodeToDelete.right)) {
976
+ // No real right child → replace with left
852
977
  replacementNode = nodeToDelete.left;
853
- this._transplant(nodeToDelete, nodeToDelete.left);
978
+ this._transplant(nodeToDelete, replacementNode);
854
979
  } else {
980
+ // Two children → find in-order successor
855
981
  const successor = this.getLeftMost(node => node, nodeToDelete.right);
856
982
  if (successor) {
857
983
  originalColor = successor.color;
858
- if (successor.right !== null) replacementNode = successor.right;
984
+ replacementNode = successor.right ?? NIL;
859
985
 
860
986
  if (successor.parent === nodeToDelete) {
861
- if (this.isRealNode(replacementNode)) {
862
- replacementNode.parent = successor;
863
- }
987
+ // Even if replacementNode is NIL, set its parent for fixup
988
+ replacementNode.parent = successor;
864
989
  } else {
865
- if (successor.right !== null) {
866
- this._transplant(successor, successor.right);
867
- successor.right = nodeToDelete.right;
868
- }
869
- if (this.isRealNode(successor.right)) {
990
+ this._transplant(successor, replacementNode);
991
+ successor.right = nodeToDelete.right;
992
+ if (successor.right) {
870
993
  successor.right.parent = successor;
871
994
  }
872
995
  }
873
996
 
874
997
  this._transplant(nodeToDelete, successor);
875
998
  successor.left = nodeToDelete.left;
876
- if (this.isRealNode(successor.left)) {
999
+ if (successor.left) {
877
1000
  successor.left.parent = successor;
878
1001
  }
879
1002
  successor.color = nodeToDelete.color;
@@ -918,6 +1041,105 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
918
1041
  * @param [thisArg] - See parameter type for details.
919
1042
  * @returns A new RedBlackTree with mapped entries.
920
1043
  */
1044
+ /**
1045
+ * Red-Black trees are self-balancing — `perfectlyBalance` rebuilds via
1046
+ * sorted bulk insert, which naturally produces a balanced RBT.
1047
+ * @remarks Time O(N), Space O(N)
1048
+
1049
+
1050
+
1051
+
1052
+
1053
+
1054
+
1055
+
1056
+
1057
+
1058
+
1059
+
1060
+
1061
+
1062
+
1063
+
1064
+
1065
+
1066
+
1067
+
1068
+
1069
+
1070
+
1071
+
1072
+
1073
+
1074
+
1075
+
1076
+ * @example
1077
+ * // Rebalance tree
1078
+ * const rbt = new RedBlackTree<number>([1, 2, 3, 4, 5]);
1079
+ * rbt.perfectlyBalance();
1080
+ * console.log(rbt.isAVLBalanced()); // true;
1081
+ */
1082
+ override perfectlyBalance(_iterationType?: IterationType): boolean {
1083
+ // Extract sorted entries, clear, re-insert — RBT self-balances on insert
1084
+ const entries: [K, V | undefined][] = [];
1085
+ for (const [key, value] of this) entries.push([key, value]);
1086
+ if (entries.length <= 1) return true;
1087
+ this.clear();
1088
+ this.setMany(
1089
+ entries.map(([k]) => k),
1090
+ entries.map(([, v]) => v),
1091
+ true // isBalanceAdd
1092
+ );
1093
+ return true;
1094
+ }
1095
+
1096
+ /**
1097
+ * Transform to new tree
1098
+
1099
+
1100
+
1101
+
1102
+
1103
+
1104
+
1105
+
1106
+
1107
+
1108
+
1109
+
1110
+
1111
+
1112
+
1113
+
1114
+
1115
+
1116
+
1117
+
1118
+
1119
+
1120
+
1121
+
1122
+
1123
+
1124
+
1125
+
1126
+
1127
+
1128
+
1129
+
1130
+
1131
+
1132
+
1133
+
1134
+
1135
+
1136
+
1137
+ * @example
1138
+ * // Transform to new tree
1139
+ * const rbt = new RedBlackTree<number, number>([[1, 10], [2, 20]]);
1140
+ * const doubled = rbt.map((v, k) => [k, (v ?? 0) * 2] as [number, number]);
1141
+ * console.log([...doubled.values()]); // [20, 40];
1142
+ */
921
1143
  override map<MK = K, MV = V, MR = any>(
922
1144
  callback: EntryCallback<K, V | undefined, [MK, MV]>,
923
1145
  options?: Partial<RedBlackTreeOptions<MK, MV, MR>>,
@@ -1131,66 +1353,76 @@ export class RedBlackTree<K = any, V = any, R = any> extends BST<K, V, R> implem
1131
1353
  * @returns void
1132
1354
  */
1133
1355
  protected _deleteFixup(node: RedBlackTreeNode<K, V> | undefined): void {
1134
- if (!node || node === this.root || node.color === 'BLACK') {
1135
- if (node) {
1136
- node.color = 'BLACK';
1137
- }
1138
- return;
1139
- }
1356
+ // Standard CLRS RB-DELETE-FIXUP: restore black-height invariant.
1357
+ // `node` is the child that replaced the deleted node (may be NIL sentinel).
1358
+ // If RED → just recolor BLACK (trivial fix). If BLACK → double-black repair.
1359
+ if (!node) return;
1140
1360
 
1141
- while (node && node !== this.root && node.color === 'BLACK') {
1142
- const parent: RedBlackTreeNode<K, V> | undefined = node.parent;
1361
+ const NIL = this.NIL;
1362
+ let current: RedBlackTreeNode<K, V> = node;
1143
1363
 
1144
- if (!parent) {
1145
- break;
1146
- }
1364
+ while (current !== this.root && current.color === 'BLACK') {
1365
+ const parent: RedBlackTreeNode<K, V> | undefined = current.parent;
1366
+ if (!parent) break;
1147
1367
 
1148
- if (node === parent.left) {
1149
- let sibling = parent.right;
1368
+ const nodeIsLeft = current === parent.left;
1369
+ let sibling = nodeIsLeft ? parent.right : parent.left;
1150
1370
 
1151
- if (sibling?.color === 'RED') {
1152
- sibling.color = 'BLACK';
1153
- parent.color = 'RED';
1371
+ // Case 1: sibling is RED → rotate to get a BLACK sibling
1372
+ if (sibling && sibling.color === 'RED') {
1373
+ sibling.color = 'BLACK';
1374
+ parent.color = 'RED';
1375
+ if (nodeIsLeft) {
1154
1376
  this._leftRotate(parent);
1155
1377
  sibling = parent.right;
1156
- }
1157
-
1158
- if ((sibling?.left?.color ?? 'BLACK') === 'BLACK') {
1159
- if (sibling) sibling.color = 'RED';
1160
- node = parent;
1161
1378
  } else {
1162
- if (sibling?.left) sibling.left.color = 'BLACK';
1163
- if (sibling) sibling.color = parent.color;
1164
- parent.color = 'BLACK';
1165
1379
  this._rightRotate(parent);
1166
- node = this.root;
1380
+ sibling = parent.left;
1167
1381
  }
1168
- } else {
1169
- let sibling = parent.left;
1382
+ }
1170
1383
 
1171
- if (sibling?.color === 'RED') {
1172
- sibling.color = 'BLACK';
1173
- if (parent) parent.color = 'RED';
1174
- this._rightRotate(parent);
1175
- if (parent) sibling = parent.left;
1176
- }
1384
+ const sibLeft = sibling?.left;
1385
+ const sibRight = sibling?.right;
1386
+ const sibLeftBlack = !sibLeft || sibLeft === NIL || sibLeft.color === 'BLACK';
1387
+ const sibRightBlack = !sibRight || sibRight === NIL || sibRight.color === 'BLACK';
1177
1388
 
1178
- if ((sibling?.right?.color ?? 'BLACK') === 'BLACK') {
1179
- if (sibling) sibling.color = 'RED';
1180
- node = parent;
1181
- } else {
1182
- if (sibling?.right) sibling.right.color = 'BLACK';
1389
+ if (sibLeftBlack && sibRightBlack) {
1390
+ // Case 2: sibling's children are both BLACK → recolor sibling RED, move up
1391
+ if (sibling) sibling.color = 'RED';
1392
+ current = parent;
1393
+ } else {
1394
+ if (nodeIsLeft) {
1395
+ // Case 3: sibling's right child is BLACK → rotate sibling right first
1396
+ if (sibRightBlack) {
1397
+ if (sibLeft) sibLeft.color = 'BLACK';
1398
+ if (sibling) sibling.color = 'RED';
1399
+ if (sibling) this._rightRotate(sibling);
1400
+ sibling = parent.right;
1401
+ }
1402
+ // Case 4: sibling's right child is RED → final rotation
1183
1403
  if (sibling) sibling.color = parent.color;
1184
- if (parent) parent.color = 'BLACK';
1404
+ parent.color = 'BLACK';
1405
+ if (sibling?.right) sibling.right.color = 'BLACK';
1185
1406
  this._leftRotate(parent);
1186
- node = this.root;
1407
+ } else {
1408
+ // Case 3 (mirror): sibling's left child is BLACK → rotate sibling left first
1409
+ if (sibLeftBlack) {
1410
+ if (sibRight) sibRight.color = 'BLACK';
1411
+ if (sibling) sibling.color = 'RED';
1412
+ if (sibling) this._leftRotate(sibling);
1413
+ sibling = parent.left;
1414
+ }
1415
+ // Case 4 (mirror): sibling's left child is RED → final rotation
1416
+ if (sibling) sibling.color = parent.color;
1417
+ parent.color = 'BLACK';
1418
+ if (sibling?.left) sibling.left.color = 'BLACK';
1419
+ this._rightRotate(parent);
1187
1420
  }
1421
+ current = this.root!;
1188
1422
  }
1189
1423
  }
1190
1424
 
1191
- if (node) {
1192
- node.color = 'BLACK';
1193
- }
1425
+ current.color = 'BLACK';
1194
1426
  }
1195
1427
 
1196
1428
  /**