pkstruct 0.1.0__py3-none-any.whl
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.
- pkstruct/__init__.py +167 -0
- pkstruct/graphs/__init__.py +127 -0
- pkstruct/graphs/connectivity.py +157 -0
- pkstruct/graphs/directed.py +95 -0
- pkstruct/graphs/exceptions.py +63 -0
- pkstruct/graphs/graph.py +262 -0
- pkstruct/graphs/mst.py +118 -0
- pkstruct/graphs/scc.py +138 -0
- pkstruct/graphs/shortest_path.py +250 -0
- pkstruct/graphs/topo_sort.py +108 -0
- pkstruct/graphs/traversal.py +175 -0
- pkstruct/graphs/visualization.py +90 -0
- pkstruct/graphs/weighted.py +37 -0
- pkstruct/linear/__init__.py +95 -0
- pkstruct/linear/deques/__init__.py +33 -0
- pkstruct/linear/deques/deque.py +194 -0
- pkstruct/linear/deques/linked_deque.py +198 -0
- pkstruct/linear/exceptions.py +26 -0
- pkstruct/linear/linked_lists/__init__.py +5 -0
- pkstruct/linear/linked_lists/_base.py +608 -0
- pkstruct/linear/linked_lists/circular_linked_list.py +230 -0
- pkstruct/linear/linked_lists/doubly_linked_list.py +151 -0
- pkstruct/linear/linked_lists/nodes.py +68 -0
- pkstruct/linear/linked_lists/singly_linked_list.py +136 -0
- pkstruct/linear/queues/__init__.py +44 -0
- pkstruct/linear/queues/circular_queue.py +258 -0
- pkstruct/linear/queues/linked_queue.py +186 -0
- pkstruct/linear/queues/priority_queue.py +202 -0
- pkstruct/linear/queues/queue.py +174 -0
- pkstruct/linear/stacks/__init__.py +38 -0
- pkstruct/linear/stacks/array_stack.py +165 -0
- pkstruct/linear/stacks/linked_stack.py +168 -0
- pkstruct/linear/stacks/stack.py +158 -0
- pkstruct/linear/utils/__init__.py +18 -0
- pkstruct/linear/utils/benchmark.py +255 -0
- pkstruct/linear/utils/debug_tools.py +239 -0
- pkstruct/linear/utils/helpers.py +143 -0
- pkstruct/linear/utils/iterators.py +148 -0
- pkstruct/linear/visualization/__init__.py +0 -0
- pkstruct/linear/visualization/ascii_visualizer.py +114 -0
- pkstruct/linear/visualization/linked_list_visualizer.py +126 -0
- pkstruct/shared/__init__.py +67 -0
- pkstruct/shared/benchmarking/__init__.py +78 -0
- pkstruct/shared/debugging/__init__.py +69 -0
- pkstruct/shared/exceptions/__init__.py +59 -0
- pkstruct/shared/serializers/__init__.py +65 -0
- pkstruct/shared/threading/__init__.py +43 -0
- pkstruct/shared/validators/__init__.py +98 -0
- pkstruct/shared/visualization/__init__.py +21 -0
- pkstruct/trees/__init__.py +92 -0
- pkstruct/trees/avl.py +321 -0
- pkstruct/trees/balancing.py +253 -0
- pkstruct/trees/bplus.py +425 -0
- pkstruct/trees/bst.py +948 -0
- pkstruct/trees/btree.py +504 -0
- pkstruct/trees/exceptions.py +96 -0
- pkstruct/trees/fenwick_tree.py +312 -0
- pkstruct/trees/interval_tree.py +541 -0
- pkstruct/trees/node.py +356 -0
- pkstruct/trees/red_black.py +710 -0
- pkstruct/trees/segment_tree.py +398 -0
- pkstruct/trees/traversal.py +456 -0
- pkstruct/trees/tree_helpers.py +366 -0
- pkstruct/trees/utils/__init__.py +15 -0
- pkstruct/trees/utils/complexity_helpers.py +231 -0
- pkstruct/trees/visualization/__init__.py +0 -0
- pkstruct/trees/visualization/ascii_renderer.py +220 -0
- pkstruct/trees/visualization/tree_printer.py +129 -0
- pkstruct-0.1.0.dist-info/METADATA +482 -0
- pkstruct-0.1.0.dist-info/RECORD +72 -0
- pkstruct-0.1.0.dist-info/WHEEL +4 -0
- pkstruct-0.1.0.dist-info/licenses/LICENSE +21 -0
pkstruct/trees/bst.py
ADDED
|
@@ -0,0 +1,948 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pkstruct.trees.bst
|
|
3
|
+
==================
|
|
4
|
+
Production-grade Binary Search Tree with a rich interview-utility API.
|
|
5
|
+
|
|
6
|
+
Architecture
|
|
7
|
+
------------
|
|
8
|
+
- Single class ``BinarySearchTree`` backed by ``TreeNode`` from ``node.py``.
|
|
9
|
+
- All recursive traversal variants are dispatched through ``_traverse()``,
|
|
10
|
+
eliminating duplication.
|
|
11
|
+
- No external dependencies; Python 3.9+ only.
|
|
12
|
+
|
|
13
|
+
Public API
|
|
14
|
+
----------
|
|
15
|
+
Core CRUD
|
|
16
|
+
insert, delete, search, contains, update, clear
|
|
17
|
+
|
|
18
|
+
Metrics
|
|
19
|
+
size, height, is_empty, min, max, floor, ceil
|
|
20
|
+
|
|
21
|
+
Navigation
|
|
22
|
+
predecessor, successor
|
|
23
|
+
|
|
24
|
+
Structural utilities
|
|
25
|
+
validate, copy, invert, is_balanced, diameter, width
|
|
26
|
+
|
|
27
|
+
Traversals (via __iter__ and explicit helpers)
|
|
28
|
+
inorder, preorder, postorder, level_order,
|
|
29
|
+
zigzag_order, vertical_order, boundary_traversal
|
|
30
|
+
|
|
31
|
+
Interview utilities
|
|
32
|
+
find_lca, kth_smallest, kth_largest, range_query,
|
|
33
|
+
path_sum, root_to_leaf_paths, serialize, deserialize
|
|
34
|
+
|
|
35
|
+
Dunder
|
|
36
|
+
__len__, __contains__, __iter__, __repr__
|
|
37
|
+
|
|
38
|
+
Complexity (h = tree height)
|
|
39
|
+
-----------------------------
|
|
40
|
+
Search O(h)
|
|
41
|
+
Insert O(h)
|
|
42
|
+
Delete O(h)
|
|
43
|
+
Space O(n)
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
from __future__ import annotations
|
|
47
|
+
|
|
48
|
+
import collections
|
|
49
|
+
import json
|
|
50
|
+
from collections.abc import Generator, Iterator
|
|
51
|
+
from typing import Any
|
|
52
|
+
|
|
53
|
+
from pkstruct.shared.threading import StructureLock
|
|
54
|
+
from pkstruct.trees.node import TreeNode
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class BinarySearchTree:
|
|
58
|
+
"""An unbalanced Binary Search Tree.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
allow_duplicates:
|
|
63
|
+
When *False* (default) inserting an existing key updates the value.
|
|
64
|
+
When *True* duplicate keys are rejected with ``ValueError``.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
# ------------------------------------------------------------------
|
|
68
|
+
# Construction
|
|
69
|
+
# ------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
def __init__(self, allow_duplicates: bool = False) -> None:
|
|
72
|
+
self._root: TreeNode | None = None
|
|
73
|
+
self._size: int = 0
|
|
74
|
+
self._allow_duplicates = allow_duplicates
|
|
75
|
+
self._lock: StructureLock = StructureLock()
|
|
76
|
+
|
|
77
|
+
# ------------------------------------------------------------------
|
|
78
|
+
# Core CRUD
|
|
79
|
+
# ------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
def insert(self, key: Any, value: Any = None) -> None:
|
|
82
|
+
"""Insert *key* with an optional *value*.
|
|
83
|
+
|
|
84
|
+
If *key* already exists and ``allow_duplicates`` is *False*, the
|
|
85
|
+
stored value is updated in-place (O(h)).
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
key:
|
|
90
|
+
Comparable key used for BST ordering.
|
|
91
|
+
value:
|
|
92
|
+
Arbitrary payload stored alongside the key.
|
|
93
|
+
|
|
94
|
+
Raises
|
|
95
|
+
------
|
|
96
|
+
ValueError
|
|
97
|
+
If ``allow_duplicates=True`` and *key* already exists.
|
|
98
|
+
"""
|
|
99
|
+
with self._lock:
|
|
100
|
+
self._root, inserted = self._insert(self._root, key, value)
|
|
101
|
+
if inserted:
|
|
102
|
+
self._size += 1
|
|
103
|
+
|
|
104
|
+
def _insert(
|
|
105
|
+
self,
|
|
106
|
+
node: TreeNode | None,
|
|
107
|
+
key: Any,
|
|
108
|
+
value: Any,
|
|
109
|
+
) -> tuple[TreeNode | None, bool]:
|
|
110
|
+
if node is None:
|
|
111
|
+
return TreeNode(key, value), True
|
|
112
|
+
current = node
|
|
113
|
+
parent: TreeNode | None = None
|
|
114
|
+
while current is not None:
|
|
115
|
+
parent = current
|
|
116
|
+
if key < current.key:
|
|
117
|
+
current = current.left
|
|
118
|
+
elif key > current.key:
|
|
119
|
+
current = current.right
|
|
120
|
+
else:
|
|
121
|
+
if self._allow_duplicates:
|
|
122
|
+
raise ValueError(f"Duplicate key: {key!r}")
|
|
123
|
+
current.value = value
|
|
124
|
+
return node, False
|
|
125
|
+
if key < parent.key: # type: ignore[union-attr]
|
|
126
|
+
parent.left = TreeNode(key, value) # type: ignore[union-attr]
|
|
127
|
+
else:
|
|
128
|
+
parent.right = TreeNode(key, value) # type: ignore[union-attr]
|
|
129
|
+
return node, True
|
|
130
|
+
|
|
131
|
+
def delete(self, key: Any) -> None:
|
|
132
|
+
"""Remove the node with *key*.
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
key:
|
|
137
|
+
Key to delete.
|
|
138
|
+
|
|
139
|
+
Raises
|
|
140
|
+
------
|
|
141
|
+
KeyError
|
|
142
|
+
If *key* is not found.
|
|
143
|
+
"""
|
|
144
|
+
with self._lock:
|
|
145
|
+
self._root, deleted = self._delete(self._root, key)
|
|
146
|
+
if not deleted:
|
|
147
|
+
raise KeyError(key)
|
|
148
|
+
self._size -= 1
|
|
149
|
+
|
|
150
|
+
def _delete(
|
|
151
|
+
self,
|
|
152
|
+
node: TreeNode | None,
|
|
153
|
+
key: Any,
|
|
154
|
+
) -> tuple[TreeNode | None, bool]:
|
|
155
|
+
if node is None:
|
|
156
|
+
return None, False
|
|
157
|
+
if key < node.key:
|
|
158
|
+
node.left, deleted = self._delete(node.left, key)
|
|
159
|
+
elif key > node.key:
|
|
160
|
+
node.right, deleted = self._delete(node.right, key)
|
|
161
|
+
else:
|
|
162
|
+
deleted = True
|
|
163
|
+
if node.left is None:
|
|
164
|
+
return node.right, deleted
|
|
165
|
+
if node.right is None:
|
|
166
|
+
return node.left, deleted
|
|
167
|
+
# Replace with in-order successor (min of right subtree)
|
|
168
|
+
successor = self._min_node(node.right)
|
|
169
|
+
node.key = successor.key
|
|
170
|
+
node.value = successor.value
|
|
171
|
+
node.right, _ = self._delete(node.right, successor.key)
|
|
172
|
+
return node, deleted
|
|
173
|
+
|
|
174
|
+
def search(self, key: Any) -> Any | None:
|
|
175
|
+
"""Return the value associated with *key*, or *None* if absent.
|
|
176
|
+
|
|
177
|
+
Parameters
|
|
178
|
+
----------
|
|
179
|
+
key:
|
|
180
|
+
Key to search.
|
|
181
|
+
|
|
182
|
+
Returns
|
|
183
|
+
-------
|
|
184
|
+
Any or None
|
|
185
|
+
"""
|
|
186
|
+
with self._lock:
|
|
187
|
+
node = self._find(self._root, key)
|
|
188
|
+
return node.value if node is not None else None
|
|
189
|
+
|
|
190
|
+
def contains(self, key: Any) -> bool:
|
|
191
|
+
"""Return *True* if *key* is present in the tree."""
|
|
192
|
+
with self._lock:
|
|
193
|
+
return self._find(self._root, key) is not None
|
|
194
|
+
|
|
195
|
+
def update(self, key: Any, value: Any) -> None:
|
|
196
|
+
"""Update the value of an existing *key*.
|
|
197
|
+
|
|
198
|
+
Parameters
|
|
199
|
+
----------
|
|
200
|
+
key:
|
|
201
|
+
Key whose value should be updated.
|
|
202
|
+
value:
|
|
203
|
+
New value.
|
|
204
|
+
|
|
205
|
+
Raises
|
|
206
|
+
------
|
|
207
|
+
KeyError
|
|
208
|
+
If *key* does not exist.
|
|
209
|
+
"""
|
|
210
|
+
with self._lock:
|
|
211
|
+
node = self._find(self._root, key)
|
|
212
|
+
if node is None:
|
|
213
|
+
raise KeyError(key)
|
|
214
|
+
node.value = value
|
|
215
|
+
|
|
216
|
+
def clear(self) -> None:
|
|
217
|
+
"""Remove all nodes from the tree."""
|
|
218
|
+
with self._lock:
|
|
219
|
+
self._root = None
|
|
220
|
+
self._size = 0
|
|
221
|
+
|
|
222
|
+
# ------------------------------------------------------------------
|
|
223
|
+
# Metrics
|
|
224
|
+
# ------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
def size(self) -> int:
|
|
227
|
+
"""Return the number of nodes currently in the tree."""
|
|
228
|
+
with self._lock:
|
|
229
|
+
return self._size
|
|
230
|
+
|
|
231
|
+
def height(self) -> int:
|
|
232
|
+
"""Return the height of the tree (0 for a single-node tree, -1 if empty)."""
|
|
233
|
+
with self._lock:
|
|
234
|
+
return self._height(self._root)
|
|
235
|
+
|
|
236
|
+
def is_empty(self) -> bool:
|
|
237
|
+
"""Return *True* if the tree contains no nodes."""
|
|
238
|
+
with self._lock:
|
|
239
|
+
return self._root is None
|
|
240
|
+
|
|
241
|
+
def min(self) -> Any:
|
|
242
|
+
"""Return the minimum key.
|
|
243
|
+
|
|
244
|
+
Raises
|
|
245
|
+
------
|
|
246
|
+
ValueError
|
|
247
|
+
If the tree is empty.
|
|
248
|
+
"""
|
|
249
|
+
with self._lock:
|
|
250
|
+
if self._root is None:
|
|
251
|
+
raise ValueError("Tree is empty")
|
|
252
|
+
return self._min_node(self._root).key
|
|
253
|
+
|
|
254
|
+
def max(self) -> Any:
|
|
255
|
+
"""Return the maximum key.
|
|
256
|
+
|
|
257
|
+
Raises
|
|
258
|
+
------
|
|
259
|
+
ValueError
|
|
260
|
+
If the tree is empty.
|
|
261
|
+
"""
|
|
262
|
+
with self._lock:
|
|
263
|
+
if self._root is None:
|
|
264
|
+
raise ValueError("Tree is empty")
|
|
265
|
+
return self._max_node(self._root).key
|
|
266
|
+
|
|
267
|
+
def floor(self, key: Any) -> Any | None:
|
|
268
|
+
"""Return the largest key less than or equal to *key*, or *None*."""
|
|
269
|
+
with self._lock:
|
|
270
|
+
node = self._floor(self._root, key)
|
|
271
|
+
return node.key if node is not None else None
|
|
272
|
+
|
|
273
|
+
def _floor(self, node: TreeNode | None, key: Any) -> TreeNode | None:
|
|
274
|
+
if node is None:
|
|
275
|
+
return None
|
|
276
|
+
if key == node.key:
|
|
277
|
+
return node
|
|
278
|
+
if key < node.key:
|
|
279
|
+
return self._floor(node.left, key)
|
|
280
|
+
right = self._floor(node.right, key)
|
|
281
|
+
return right if right is not None else node
|
|
282
|
+
|
|
283
|
+
def _ceil(self, node: TreeNode | None, key: Any) -> TreeNode | None:
|
|
284
|
+
if node is None:
|
|
285
|
+
return None
|
|
286
|
+
if key == node.key:
|
|
287
|
+
return node
|
|
288
|
+
if key > node.key:
|
|
289
|
+
return self._ceil(node.right, key)
|
|
290
|
+
left = self._ceil(node.left, key)
|
|
291
|
+
return left if left is not None else node
|
|
292
|
+
|
|
293
|
+
def ceil(self, key: Any) -> Any | None:
|
|
294
|
+
"""Return the smallest key greater than or equal to *key*, or *None*."""
|
|
295
|
+
with self._lock:
|
|
296
|
+
node = self._ceil(self._root, key)
|
|
297
|
+
return node.key if node is not None else None
|
|
298
|
+
|
|
299
|
+
# ------------------------------------------------------------------
|
|
300
|
+
# Navigation
|
|
301
|
+
# ------------------------------------------------------------------
|
|
302
|
+
|
|
303
|
+
def predecessor(self, key: Any) -> Any | None:
|
|
304
|
+
"""Return the in-order predecessor key of *key*, or *None*.
|
|
305
|
+
|
|
306
|
+
Raises
|
|
307
|
+
------
|
|
308
|
+
KeyError
|
|
309
|
+
If *key* is not in the tree.
|
|
310
|
+
"""
|
|
311
|
+
with self._lock:
|
|
312
|
+
if not self.contains(key):
|
|
313
|
+
raise KeyError(key)
|
|
314
|
+
pred: TreeNode | None = None
|
|
315
|
+
node = self._root
|
|
316
|
+
while node is not None:
|
|
317
|
+
if key < node.key:
|
|
318
|
+
node = node.left
|
|
319
|
+
elif key > node.key:
|
|
320
|
+
pred = node
|
|
321
|
+
node = node.right
|
|
322
|
+
else:
|
|
323
|
+
if node.left is not None:
|
|
324
|
+
pred = self._max_node(node.left)
|
|
325
|
+
break
|
|
326
|
+
return pred.key if pred is not None else None
|
|
327
|
+
|
|
328
|
+
def successor(self, key: Any) -> Any | None:
|
|
329
|
+
"""Return the in-order successor key of *key*, or *None*.
|
|
330
|
+
|
|
331
|
+
Raises
|
|
332
|
+
------
|
|
333
|
+
KeyError
|
|
334
|
+
If *key* is not in the tree.
|
|
335
|
+
"""
|
|
336
|
+
with self._lock:
|
|
337
|
+
if not self.contains(key):
|
|
338
|
+
raise KeyError(key)
|
|
339
|
+
succ: TreeNode | None = None
|
|
340
|
+
node = self._root
|
|
341
|
+
while node is not None:
|
|
342
|
+
if key > node.key:
|
|
343
|
+
node = node.right
|
|
344
|
+
elif key < node.key:
|
|
345
|
+
succ = node
|
|
346
|
+
node = node.left
|
|
347
|
+
else:
|
|
348
|
+
if node.right is not None:
|
|
349
|
+
succ = self._min_node(node.right)
|
|
350
|
+
break
|
|
351
|
+
return succ.key if succ is not None else None
|
|
352
|
+
|
|
353
|
+
# ------------------------------------------------------------------
|
|
354
|
+
# Structural utilities
|
|
355
|
+
# ------------------------------------------------------------------
|
|
356
|
+
|
|
357
|
+
def validate(self) -> bool:
|
|
358
|
+
"""Verify BST ordering invariant for every node.
|
|
359
|
+
|
|
360
|
+
Returns
|
|
361
|
+
-------
|
|
362
|
+
bool
|
|
363
|
+
*True* if all nodes satisfy the BST property.
|
|
364
|
+
"""
|
|
365
|
+
with self._lock:
|
|
366
|
+
return self._validate(self._root, None, None)
|
|
367
|
+
|
|
368
|
+
def _validate(
|
|
369
|
+
self,
|
|
370
|
+
node: TreeNode | None,
|
|
371
|
+
lo: Any,
|
|
372
|
+
hi: Any,
|
|
373
|
+
) -> bool:
|
|
374
|
+
if node is None:
|
|
375
|
+
return True
|
|
376
|
+
if lo is not None and node.key <= lo:
|
|
377
|
+
return False
|
|
378
|
+
if hi is not None and node.key >= hi:
|
|
379
|
+
return False
|
|
380
|
+
return self._validate(node.left, lo, node.key) and self._validate(node.right, node.key, hi)
|
|
381
|
+
|
|
382
|
+
def copy(self) -> BinarySearchTree:
|
|
383
|
+
"""Return a deep copy of this tree (new nodes, same key/value pairs)."""
|
|
384
|
+
with self._lock:
|
|
385
|
+
new_tree: BinarySearchTree = BinarySearchTree(allow_duplicates=self._allow_duplicates)
|
|
386
|
+
new_tree._root = self._copy_node(self._root)
|
|
387
|
+
new_tree._size = self._size
|
|
388
|
+
return new_tree
|
|
389
|
+
|
|
390
|
+
def _copy_node(self, node: TreeNode | None) -> TreeNode | None:
|
|
391
|
+
if node is None:
|
|
392
|
+
return None
|
|
393
|
+
new_node = TreeNode(node.key, node.value)
|
|
394
|
+
new_node.left = self._copy_node(node.left)
|
|
395
|
+
new_node.right = self._copy_node(node.right)
|
|
396
|
+
return new_node
|
|
397
|
+
|
|
398
|
+
def invert(self) -> None:
|
|
399
|
+
"""Mirror the tree in-place (swap left/right children at every node)."""
|
|
400
|
+
with self._lock:
|
|
401
|
+
self._invert(self._root)
|
|
402
|
+
|
|
403
|
+
def _invert(self, node: TreeNode | None) -> None:
|
|
404
|
+
if node is None:
|
|
405
|
+
return
|
|
406
|
+
node.left, node.right = node.right, node.left
|
|
407
|
+
self._invert(node.left)
|
|
408
|
+
self._invert(node.right)
|
|
409
|
+
|
|
410
|
+
def is_balanced(self) -> bool:
|
|
411
|
+
"""Return *True* if the tree is height-balanced (|left_h - right_h| ≤ 1 for every node)."""
|
|
412
|
+
with self._lock:
|
|
413
|
+
return self._check_balanced(self._root) != -2
|
|
414
|
+
|
|
415
|
+
def _check_balanced(self, node: TreeNode | None) -> int:
|
|
416
|
+
"""Return height if balanced, -2 as sentinel for unbalanced."""
|
|
417
|
+
if node is None:
|
|
418
|
+
return -1
|
|
419
|
+
lh = self._check_balanced(node.left)
|
|
420
|
+
if lh == -2:
|
|
421
|
+
return -2
|
|
422
|
+
rh = self._check_balanced(node.right)
|
|
423
|
+
if rh == -2:
|
|
424
|
+
return -2
|
|
425
|
+
if abs(lh - rh) > 1:
|
|
426
|
+
return -2
|
|
427
|
+
return max(lh, rh) + 1
|
|
428
|
+
|
|
429
|
+
def diameter(self) -> int:
|
|
430
|
+
"""Return the diameter (longest path between any two nodes, in edges)."""
|
|
431
|
+
with self._lock:
|
|
432
|
+
self._diameter_max: int = 0
|
|
433
|
+
self._diameter_helper(self._root)
|
|
434
|
+
return self._diameter_max
|
|
435
|
+
|
|
436
|
+
def _diameter_helper(self, node: TreeNode | None) -> int:
|
|
437
|
+
if node is None:
|
|
438
|
+
return -1
|
|
439
|
+
lh = self._diameter_helper(node.left) + 1
|
|
440
|
+
rh = self._diameter_helper(node.right) + 1
|
|
441
|
+
self._diameter_max = max(self._diameter_max, lh + rh)
|
|
442
|
+
return max(lh, rh)
|
|
443
|
+
|
|
444
|
+
def width(self) -> int:
|
|
445
|
+
"""Return the maximum width (number of nodes) across all levels."""
|
|
446
|
+
with self._lock:
|
|
447
|
+
if self._root is None:
|
|
448
|
+
return 0
|
|
449
|
+
max_w = 0
|
|
450
|
+
queue: collections.deque[TreeNode] = collections.deque([self._root])
|
|
451
|
+
while queue:
|
|
452
|
+
level_size = len(queue)
|
|
453
|
+
max_w = max(max_w, level_size)
|
|
454
|
+
for _ in range(level_size):
|
|
455
|
+
node = queue.popleft()
|
|
456
|
+
if node.left:
|
|
457
|
+
queue.append(node.left)
|
|
458
|
+
if node.right:
|
|
459
|
+
queue.append(node.right)
|
|
460
|
+
return max_w
|
|
461
|
+
|
|
462
|
+
# ------------------------------------------------------------------
|
|
463
|
+
# Interview utilities
|
|
464
|
+
# ------------------------------------------------------------------
|
|
465
|
+
|
|
466
|
+
def find_lca(self, key1: Any, key2: Any) -> Any | None:
|
|
467
|
+
"""Return the key of the Lowest Common Ancestor of *key1* and *key2*.
|
|
468
|
+
|
|
469
|
+
Parameters
|
|
470
|
+
----------
|
|
471
|
+
key1, key2:
|
|
472
|
+
Both must exist in the tree.
|
|
473
|
+
|
|
474
|
+
Raises
|
|
475
|
+
------
|
|
476
|
+
KeyError
|
|
477
|
+
If either key is absent.
|
|
478
|
+
"""
|
|
479
|
+
with self._lock:
|
|
480
|
+
for k in (key1, key2):
|
|
481
|
+
if not self.contains(k):
|
|
482
|
+
raise KeyError(k)
|
|
483
|
+
node = self._lca(self._root, key1, key2)
|
|
484
|
+
return node.key if node is not None else None
|
|
485
|
+
|
|
486
|
+
def _lca(
|
|
487
|
+
self,
|
|
488
|
+
node: TreeNode | None,
|
|
489
|
+
k1: Any,
|
|
490
|
+
k2: Any,
|
|
491
|
+
) -> TreeNode | None:
|
|
492
|
+
if node is None:
|
|
493
|
+
return None
|
|
494
|
+
if k1 < node.key and k2 < node.key:
|
|
495
|
+
return self._lca(node.left, k1, k2)
|
|
496
|
+
if k1 > node.key and k2 > node.key:
|
|
497
|
+
return self._lca(node.right, k1, k2)
|
|
498
|
+
return node
|
|
499
|
+
|
|
500
|
+
def kth_smallest(self, k: int) -> Any:
|
|
501
|
+
"""Return the k-th smallest key (1-indexed).
|
|
502
|
+
|
|
503
|
+
Raises
|
|
504
|
+
------
|
|
505
|
+
ValueError
|
|
506
|
+
If *k* is out of range.
|
|
507
|
+
"""
|
|
508
|
+
with self._lock:
|
|
509
|
+
result: list[Any] = []
|
|
510
|
+
self._inorder_collect(self._root, result, k)
|
|
511
|
+
if k < 1 or k > len(result):
|
|
512
|
+
raise ValueError(f"k={k} is out of range [1, {self._size}]")
|
|
513
|
+
return result[k - 1]
|
|
514
|
+
|
|
515
|
+
def kth_largest(self, k: int) -> Any:
|
|
516
|
+
"""Return the k-th largest key (1-indexed).
|
|
517
|
+
|
|
518
|
+
Raises
|
|
519
|
+
------
|
|
520
|
+
ValueError
|
|
521
|
+
If *k* is out of range.
|
|
522
|
+
"""
|
|
523
|
+
with self._lock:
|
|
524
|
+
if k < 1 or k > self._size:
|
|
525
|
+
raise ValueError(f"k={k} is out of range [1, {self._size}]")
|
|
526
|
+
# Reverse in-order (right → root → left)
|
|
527
|
+
result: list[Any] = []
|
|
528
|
+
self._reverse_inorder_collect(self._root, result, k)
|
|
529
|
+
return result[k - 1]
|
|
530
|
+
|
|
531
|
+
def _reverse_inorder_collect(
|
|
532
|
+
self,
|
|
533
|
+
node: TreeNode | None,
|
|
534
|
+
result: list,
|
|
535
|
+
limit: int,
|
|
536
|
+
) -> None:
|
|
537
|
+
if node is None or len(result) >= limit:
|
|
538
|
+
return
|
|
539
|
+
self._reverse_inorder_collect(node.right, result, limit)
|
|
540
|
+
if len(result) < limit:
|
|
541
|
+
result.append(node.key)
|
|
542
|
+
self._reverse_inorder_collect(node.left, result, limit)
|
|
543
|
+
|
|
544
|
+
def range_query(self, lo: Any, hi: Any) -> list[Any]:
|
|
545
|
+
"""Return all keys in [*lo*, *hi*] in sorted order.
|
|
546
|
+
|
|
547
|
+
Parameters
|
|
548
|
+
----------
|
|
549
|
+
lo, hi:
|
|
550
|
+
Inclusive lower and upper bounds.
|
|
551
|
+
"""
|
|
552
|
+
with self._lock:
|
|
553
|
+
result: list[Any] = []
|
|
554
|
+
self._range_collect(self._root, lo, hi, result)
|
|
555
|
+
return result
|
|
556
|
+
|
|
557
|
+
def _range_collect(
|
|
558
|
+
self,
|
|
559
|
+
node: TreeNode | None,
|
|
560
|
+
lo: Any,
|
|
561
|
+
hi: Any,
|
|
562
|
+
result: list,
|
|
563
|
+
) -> None:
|
|
564
|
+
if node is None:
|
|
565
|
+
return
|
|
566
|
+
if lo < node.key:
|
|
567
|
+
self._range_collect(node.left, lo, hi, result)
|
|
568
|
+
if lo <= node.key <= hi:
|
|
569
|
+
result.append(node.key)
|
|
570
|
+
if hi > node.key:
|
|
571
|
+
self._range_collect(node.right, lo, hi, result)
|
|
572
|
+
|
|
573
|
+
def path_sum(self, target: int | float) -> bool:
|
|
574
|
+
"""Return *True* if any root-to-leaf path sums to *target*.
|
|
575
|
+
|
|
576
|
+
Assumes keys are numeric.
|
|
577
|
+
"""
|
|
578
|
+
with self._lock:
|
|
579
|
+
return self._path_sum(self._root, target, 0)
|
|
580
|
+
|
|
581
|
+
def _path_sum(
|
|
582
|
+
self,
|
|
583
|
+
node: TreeNode | None,
|
|
584
|
+
target: int | float,
|
|
585
|
+
current: int | float,
|
|
586
|
+
) -> bool:
|
|
587
|
+
if node is None:
|
|
588
|
+
return False
|
|
589
|
+
current += node.key
|
|
590
|
+
if node.left is None and node.right is None:
|
|
591
|
+
return current == target
|
|
592
|
+
return self._path_sum(node.left, target, current) or self._path_sum(
|
|
593
|
+
node.right, target, current
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
def root_to_leaf_paths(self) -> list[list[Any]]:
|
|
597
|
+
"""Return all root-to-leaf paths as lists of keys."""
|
|
598
|
+
with self._lock:
|
|
599
|
+
paths: list[list[Any]] = []
|
|
600
|
+
self._collect_paths(self._root, [], paths)
|
|
601
|
+
return paths
|
|
602
|
+
|
|
603
|
+
def _collect_paths(
|
|
604
|
+
self,
|
|
605
|
+
node: TreeNode | None,
|
|
606
|
+
current: list[Any],
|
|
607
|
+
paths: list[list[Any]],
|
|
608
|
+
) -> None:
|
|
609
|
+
if node is None:
|
|
610
|
+
return
|
|
611
|
+
current.append(node.key)
|
|
612
|
+
if node.left is None and node.right is None:
|
|
613
|
+
paths.append(list(current))
|
|
614
|
+
else:
|
|
615
|
+
self._collect_paths(node.left, current, paths)
|
|
616
|
+
self._collect_paths(node.right, current, paths)
|
|
617
|
+
current.pop()
|
|
618
|
+
|
|
619
|
+
def serialize(self) -> str:
|
|
620
|
+
"""Serialize the tree to a JSON string (level-order with null sentinels).
|
|
621
|
+
|
|
622
|
+
Returns
|
|
623
|
+
-------
|
|
624
|
+
str
|
|
625
|
+
JSON representation that can be passed to :meth:`deserialize`.
|
|
626
|
+
"""
|
|
627
|
+
with self._lock:
|
|
628
|
+
if self._root is None:
|
|
629
|
+
return "[]"
|
|
630
|
+
result: list[Any | None] = []
|
|
631
|
+
queue: collections.deque[TreeNode | None] = collections.deque([self._root])
|
|
632
|
+
while queue:
|
|
633
|
+
node = queue.popleft()
|
|
634
|
+
if node is None:
|
|
635
|
+
result.append(None)
|
|
636
|
+
else:
|
|
637
|
+
result.append(node.key)
|
|
638
|
+
queue.append(node.left)
|
|
639
|
+
queue.append(node.right)
|
|
640
|
+
# Trim trailing nulls
|
|
641
|
+
while result and result[-1] is None:
|
|
642
|
+
result.pop()
|
|
643
|
+
return json.dumps(result)
|
|
644
|
+
|
|
645
|
+
def deserialize(self, data: str) -> None:
|
|
646
|
+
"""Rebuild the tree from a JSON string produced by :meth:`serialize`.
|
|
647
|
+
|
|
648
|
+
Clears any existing content before loading.
|
|
649
|
+
|
|
650
|
+
Parameters
|
|
651
|
+
----------
|
|
652
|
+
data:
|
|
653
|
+
JSON string as returned by :meth:`serialize`.
|
|
654
|
+
"""
|
|
655
|
+
with self._lock:
|
|
656
|
+
self.clear()
|
|
657
|
+
keys: list[Any | None] = json.loads(data)
|
|
658
|
+
if not keys:
|
|
659
|
+
return
|
|
660
|
+
self._root = TreeNode(keys[0])
|
|
661
|
+
self._size = 1
|
|
662
|
+
queue: collections.deque[TreeNode] = collections.deque([self._root])
|
|
663
|
+
i = 1
|
|
664
|
+
while queue and i < len(keys):
|
|
665
|
+
node = queue.popleft()
|
|
666
|
+
if i < len(keys) and keys[i] is not None:
|
|
667
|
+
node.left = TreeNode(keys[i])
|
|
668
|
+
self._size += 1
|
|
669
|
+
queue.append(node.left)
|
|
670
|
+
i += 1
|
|
671
|
+
if i < len(keys) and keys[i] is not None:
|
|
672
|
+
node.right = TreeNode(keys[i])
|
|
673
|
+
self._size += 1
|
|
674
|
+
queue.append(node.right)
|
|
675
|
+
i += 1
|
|
676
|
+
|
|
677
|
+
def boundary_traversal(self) -> list[Any]:
|
|
678
|
+
"""Return keys in boundary order: left boundary + leaves + right boundary (reversed).
|
|
679
|
+
|
|
680
|
+
The result traces the outer edge of the tree anti-clockwise starting
|
|
681
|
+
from the root.
|
|
682
|
+
"""
|
|
683
|
+
with self._lock:
|
|
684
|
+
if self._root is None:
|
|
685
|
+
return []
|
|
686
|
+
result: list[Any] = [self._root.key]
|
|
687
|
+
self._left_boundary(self._root.left, result)
|
|
688
|
+
self._leaves(self._root.left, result)
|
|
689
|
+
self._leaves(self._root.right, result)
|
|
690
|
+
self._right_boundary(self._root.right, result)
|
|
691
|
+
return result
|
|
692
|
+
|
|
693
|
+
def vertical_order(self) -> list[list[Any]]:
|
|
694
|
+
"""Return keys grouped by vertical column, left to right.
|
|
695
|
+
|
|
696
|
+
Each inner list contains the keys at the same horizontal distance
|
|
697
|
+
from the root, sorted top-to-bottom within the column.
|
|
698
|
+
"""
|
|
699
|
+
with self._lock:
|
|
700
|
+
if self._root is None:
|
|
701
|
+
return []
|
|
702
|
+
col_map: dict[int, list[Any]] = collections.defaultdict(list)
|
|
703
|
+
queue: collections.deque[tuple[TreeNode, int]] = collections.deque([(self._root, 0)])
|
|
704
|
+
while queue:
|
|
705
|
+
node, col = queue.popleft()
|
|
706
|
+
col_map[col].append(node.key)
|
|
707
|
+
if node.left:
|
|
708
|
+
queue.append((node.left, col - 1))
|
|
709
|
+
if node.right:
|
|
710
|
+
queue.append((node.right, col + 1))
|
|
711
|
+
return [col_map[c] for c in sorted(col_map)]
|
|
712
|
+
|
|
713
|
+
def zigzag_order(self) -> list[list[Any]]:
|
|
714
|
+
"""Return keys level by level, alternating left-to-right and right-to-left."""
|
|
715
|
+
with self._lock:
|
|
716
|
+
if self._root is None:
|
|
717
|
+
return []
|
|
718
|
+
result: list[list[Any]] = []
|
|
719
|
+
queue: collections.deque[TreeNode] = collections.deque([self._root])
|
|
720
|
+
left_to_right = True
|
|
721
|
+
while queue:
|
|
722
|
+
level_size = len(queue)
|
|
723
|
+
level: collections.deque[Any] = collections.deque()
|
|
724
|
+
for _ in range(level_size):
|
|
725
|
+
node = queue.popleft()
|
|
726
|
+
if left_to_right:
|
|
727
|
+
level.append(node.key)
|
|
728
|
+
else:
|
|
729
|
+
level.appendleft(node.key)
|
|
730
|
+
if node.left:
|
|
731
|
+
queue.append(node.left)
|
|
732
|
+
if node.right:
|
|
733
|
+
queue.append(node.right)
|
|
734
|
+
result.append(list(level))
|
|
735
|
+
left_to_right = not left_to_right
|
|
736
|
+
return result
|
|
737
|
+
|
|
738
|
+
def _left_boundary(self, node: TreeNode | None, result: list) -> None:
|
|
739
|
+
if node is None or (node.left is None and node.right is None):
|
|
740
|
+
return
|
|
741
|
+
result.append(node.key)
|
|
742
|
+
if node.left:
|
|
743
|
+
self._left_boundary(node.left, result)
|
|
744
|
+
else:
|
|
745
|
+
self._left_boundary(node.right, result)
|
|
746
|
+
|
|
747
|
+
def _right_boundary(self, node: TreeNode | None, result: list) -> None:
|
|
748
|
+
if node is None or (node.left is None and node.right is None):
|
|
749
|
+
return
|
|
750
|
+
if node.right:
|
|
751
|
+
self._right_boundary(node.right, result)
|
|
752
|
+
else:
|
|
753
|
+
self._right_boundary(node.left, result)
|
|
754
|
+
result.append(node.key)
|
|
755
|
+
|
|
756
|
+
def _leaves(self, node: TreeNode | None, result: list) -> None:
|
|
757
|
+
if node is None:
|
|
758
|
+
return
|
|
759
|
+
if node.left is None and node.right is None:
|
|
760
|
+
result.append(node.key)
|
|
761
|
+
return
|
|
762
|
+
self._leaves(node.left, result)
|
|
763
|
+
self._leaves(node.right, result)
|
|
764
|
+
|
|
765
|
+
def vertical_order(self) -> list[list[Any]]:
|
|
766
|
+
"""Return keys grouped by vertical column, left to right.
|
|
767
|
+
|
|
768
|
+
Each inner list contains the keys at the same horizontal distance
|
|
769
|
+
from the root, sorted top-to-bottom within the column.
|
|
770
|
+
"""
|
|
771
|
+
if self._root is None:
|
|
772
|
+
return []
|
|
773
|
+
col_map: dict[int, list[Any]] = collections.defaultdict(list)
|
|
774
|
+
queue: collections.deque[tuple[TreeNode, int]] = collections.deque([(self._root, 0)])
|
|
775
|
+
while queue:
|
|
776
|
+
node, col = queue.popleft()
|
|
777
|
+
col_map[col].append(node.key)
|
|
778
|
+
if node.left:
|
|
779
|
+
queue.append((node.left, col - 1))
|
|
780
|
+
if node.right:
|
|
781
|
+
queue.append((node.right, col + 1))
|
|
782
|
+
return [col_map[c] for c in sorted(col_map)]
|
|
783
|
+
|
|
784
|
+
def zigzag_order(self) -> list[list[Any]]:
|
|
785
|
+
"""Return keys level by level, alternating left-to-right and right-to-left."""
|
|
786
|
+
if self._root is None:
|
|
787
|
+
return []
|
|
788
|
+
result: list[list[Any]] = []
|
|
789
|
+
queue: collections.deque[TreeNode] = collections.deque([self._root])
|
|
790
|
+
left_to_right = True
|
|
791
|
+
while queue:
|
|
792
|
+
level_size = len(queue)
|
|
793
|
+
level: collections.deque[Any] = collections.deque()
|
|
794
|
+
for _ in range(level_size):
|
|
795
|
+
node = queue.popleft()
|
|
796
|
+
if left_to_right:
|
|
797
|
+
level.append(node.key)
|
|
798
|
+
else:
|
|
799
|
+
level.appendleft(node.key)
|
|
800
|
+
if node.left:
|
|
801
|
+
queue.append(node.left)
|
|
802
|
+
if node.right:
|
|
803
|
+
queue.append(node.right)
|
|
804
|
+
result.append(list(level))
|
|
805
|
+
left_to_right = not left_to_right
|
|
806
|
+
return result
|
|
807
|
+
|
|
808
|
+
# ------------------------------------------------------------------
|
|
809
|
+
# Traversal helpers
|
|
810
|
+
# ------------------------------------------------------------------
|
|
811
|
+
|
|
812
|
+
def _traverse(
|
|
813
|
+
self,
|
|
814
|
+
order: str = "inorder",
|
|
815
|
+
) -> Generator[Any, None, None]:
|
|
816
|
+
"""Unified traversal generator.
|
|
817
|
+
|
|
818
|
+
Parameters
|
|
819
|
+
----------
|
|
820
|
+
order:
|
|
821
|
+
One of ``"inorder"``, ``"preorder"``, ``"postorder"``,
|
|
822
|
+
``"levelorder"``.
|
|
823
|
+
"""
|
|
824
|
+
if order == "inorder":
|
|
825
|
+
yield from self._inorder(self._root)
|
|
826
|
+
elif order == "preorder":
|
|
827
|
+
yield from self._preorder(self._root)
|
|
828
|
+
elif order == "postorder":
|
|
829
|
+
yield from self._postorder(self._root)
|
|
830
|
+
elif order == "levelorder":
|
|
831
|
+
yield from self._levelorder()
|
|
832
|
+
else:
|
|
833
|
+
raise ValueError(f"Unknown traversal order: {order!r}")
|
|
834
|
+
|
|
835
|
+
def _inorder(self, node: TreeNode | None) -> Generator[Any, None, None]:
|
|
836
|
+
stack: list[TreeNode] = []
|
|
837
|
+
current = node
|
|
838
|
+
while stack or current:
|
|
839
|
+
while current:
|
|
840
|
+
stack.append(current)
|
|
841
|
+
current = current.left
|
|
842
|
+
current = stack.pop()
|
|
843
|
+
yield current.key
|
|
844
|
+
current = current.right
|
|
845
|
+
|
|
846
|
+
def _preorder(self, node: TreeNode | None) -> Generator[Any, None, None]:
|
|
847
|
+
if node is None:
|
|
848
|
+
return
|
|
849
|
+
stack: list[TreeNode] = [node]
|
|
850
|
+
while stack:
|
|
851
|
+
current = stack.pop()
|
|
852
|
+
yield current.key
|
|
853
|
+
if current.right:
|
|
854
|
+
stack.append(current.right)
|
|
855
|
+
if current.left:
|
|
856
|
+
stack.append(current.left)
|
|
857
|
+
|
|
858
|
+
def _postorder(self, node: TreeNode | None) -> Generator[Any, None, None]:
|
|
859
|
+
if node is None:
|
|
860
|
+
return
|
|
861
|
+
stack1: list[TreeNode] = [node]
|
|
862
|
+
stack2: list[TreeNode] = []
|
|
863
|
+
while stack1:
|
|
864
|
+
current = stack1.pop()
|
|
865
|
+
stack2.append(current)
|
|
866
|
+
if current.left:
|
|
867
|
+
stack1.append(current.left)
|
|
868
|
+
if current.right:
|
|
869
|
+
stack1.append(current.right)
|
|
870
|
+
while stack2:
|
|
871
|
+
yield stack2.pop().key
|
|
872
|
+
|
|
873
|
+
def _levelorder(self) -> Generator[Any, None, None]:
|
|
874
|
+
if self._root is None:
|
|
875
|
+
return
|
|
876
|
+
queue: collections.deque[TreeNode] = collections.deque([self._root])
|
|
877
|
+
while queue:
|
|
878
|
+
node = queue.popleft()
|
|
879
|
+
yield node.key
|
|
880
|
+
if node.left:
|
|
881
|
+
queue.append(node.left)
|
|
882
|
+
if node.right:
|
|
883
|
+
queue.append(node.right)
|
|
884
|
+
|
|
885
|
+
def _inorder_collect(
|
|
886
|
+
self,
|
|
887
|
+
node: TreeNode | None,
|
|
888
|
+
result: list,
|
|
889
|
+
limit: int,
|
|
890
|
+
) -> None:
|
|
891
|
+
if node is None or len(result) >= limit:
|
|
892
|
+
return
|
|
893
|
+
self._inorder_collect(node.left, result, limit)
|
|
894
|
+
if len(result) < limit:
|
|
895
|
+
result.append(node.key)
|
|
896
|
+
self._inorder_collect(node.right, result, limit)
|
|
897
|
+
|
|
898
|
+
# ------------------------------------------------------------------
|
|
899
|
+
# Private helpers
|
|
900
|
+
# ------------------------------------------------------------------
|
|
901
|
+
|
|
902
|
+
def _find(self, node: TreeNode | None, key: Any) -> TreeNode | None:
|
|
903
|
+
while node is not None:
|
|
904
|
+
if key < node.key:
|
|
905
|
+
node = node.left
|
|
906
|
+
elif key > node.key:
|
|
907
|
+
node = node.right
|
|
908
|
+
else:
|
|
909
|
+
return node
|
|
910
|
+
return None
|
|
911
|
+
|
|
912
|
+
def _height(self, node: TreeNode | None) -> int:
|
|
913
|
+
if node is None:
|
|
914
|
+
return -1
|
|
915
|
+
return 1 + max(self._height(node.left), self._height(node.right))
|
|
916
|
+
|
|
917
|
+
def _min_node(self, node: TreeNode) -> TreeNode:
|
|
918
|
+
while node.left is not None:
|
|
919
|
+
node = node.left
|
|
920
|
+
return node
|
|
921
|
+
|
|
922
|
+
def _max_node(self, node: TreeNode) -> TreeNode:
|
|
923
|
+
while node.right is not None:
|
|
924
|
+
node = node.right
|
|
925
|
+
return node
|
|
926
|
+
|
|
927
|
+
# ------------------------------------------------------------------
|
|
928
|
+
# Dunder methods
|
|
929
|
+
# ------------------------------------------------------------------
|
|
930
|
+
|
|
931
|
+
def __len__(self) -> int:
|
|
932
|
+
"""Return the number of nodes in the tree."""
|
|
933
|
+
return self.size()
|
|
934
|
+
|
|
935
|
+
def __contains__(self, key: Any) -> bool:
|
|
936
|
+
"""Support ``key in tree`` syntax."""
|
|
937
|
+
return self.contains(key)
|
|
938
|
+
|
|
939
|
+
def __iter__(self) -> Iterator[Any]:
|
|
940
|
+
"""Iterate over keys in ascending (in-order) order."""
|
|
941
|
+
with self._lock:
|
|
942
|
+
keys = list(self._traverse("inorder"))
|
|
943
|
+
return iter(keys)
|
|
944
|
+
|
|
945
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
946
|
+
with self._lock:
|
|
947
|
+
keys = list(self._traverse("inorder"))
|
|
948
|
+
return f"BinarySearchTree(size={self._size}, keys={keys})"
|