-
-
Notifications
You must be signed in to change notification settings - Fork 50.5k
Expand file tree
/
Copy pathsplay_tree.py
More file actions
334 lines (270 loc) · 9.77 KB
/
splay_tree.py
File metadata and controls
334 lines (270 loc) · 9.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
"""
Splay Tree implementation - A self-adjusting binary search tree.
A Splay tree is a self-adjusting binary search tree where recently accessed
elements are moved to the root through rotations. This provides amortized
O(log n) time complexity for search, insert, and delete operations.
The splaying operation moves a node to the root by performing a series of
rotations, making frequently accessed elements faster to access in the future.
For more information, see: https://en.wikipedia.org/wiki/Splay_tree
Time Complexity (amortized):
- Search: O(log n)
- Insert: O(log n)
- Delete: O(log n)
Space Complexity: O(n)
Operations:
- Zig: Single rotation (when parent is root)
- Zig-zig: Double rotation in same direction
- Zig-zag: Double rotation in opposite directions
Example:
>>> tree = SplayTree()
>>> _ = tree.insert(10)
>>> _ = tree.insert(20)
>>> _ = tree.insert(30)
>>> _ = tree.insert(5)
>>> _ = tree.insert(15)
>>> list(tree.inorder())
[5, 10, 15, 20, 30]
>>> tree.search(15)
True
>>> tree.search(25)
False
>>> _ = tree.delete(20)
>>> list(tree.inorder())
[5, 10, 15, 30]
"""
from __future__ import annotations
from collections.abc import Iterator
from dataclasses import dataclass
from typing import Any, Self
@dataclass
class SplayNode:
"""A node in the Splay Tree."""
value: Any
left: SplayNode | None = None
right: SplayNode | None = None
parent: SplayNode | None = None
def __iter__(self) -> Iterator[Any]:
"""Inorder traversal iterator."""
yield from self.left or []
yield self.value
yield from self.right or []
def __repr__(self) -> str:
from pprint import pformat
if self.left is None and self.right is None:
return str(self.value)
return pformat({f"{self.value}": (self.left, self.right)}, indent=1)
@property
def is_right(self) -> bool:
"""Check if this node is the right child of its parent."""
return bool(self.parent and self is self.parent.right)
@dataclass
class SplayTree:
"""
Splay Tree implementation - A self-adjusting BST.
This tree automatically moves recently accessed elements to the root
through rotations, providing amortized O(log n) performance for all operations.
"""
root: SplayNode | None = None
def __bool__(self) -> bool:
"""Return True if the tree is not empty."""
return self.root is not None
def __iter__(self) -> Iterator[Any]:
"""Iterate over the tree in inorder traversal."""
yield from self.root or []
def __len__(self) -> int:
"""Return the number of nodes in the tree."""
return sum(1 for _ in self)
def __str__(self) -> str:
"""Return a string representation of the tree."""
return str(self.root)
def _rotate_right(self, node: SplayNode) -> None:
"""Perform a right rotation on the given node."""
if not node.left:
return
left_child = node.left
node.left = left_child.right
if left_child.right:
left_child.right.parent = node
left_child.parent = node.parent
if not node.parent:
self.root = left_child
elif node is node.parent.right:
node.parent.right = left_child
else:
node.parent.left = left_child
left_child.right = node
node.parent = left_child
def _rotate_left(self, node: SplayNode) -> None:
"""Perform a left rotation on the given node."""
if not node.right:
return
right_child = node.right
node.right = right_child.left
if right_child.left:
right_child.left.parent = node
right_child.parent = node.parent
if not node.parent:
self.root = right_child
elif node is node.parent.left:
node.parent.left = right_child
else:
node.parent.right = right_child
right_child.left = node
node.parent = right_child
def _splay(self, node: SplayNode) -> None:
"""
Splay the given node to the root through a series of rotations.
The splaying operation uses three types of rotations:
- Zig: Single rotation when parent is root
- Zig-zig: Double rotation in same direction
- Zig-zag: Double rotation in opposite directions
"""
while node.parent:
parent = node.parent
grandparent = parent.parent
if not grandparent:
# Zig case: parent is root
if node is parent.left:
self._rotate_right(parent)
else:
self._rotate_left(parent)
elif (node is parent.left and parent is grandparent.left) or (
node is parent.right and parent is grandparent.right
):
# Zig-zig case: same direction
if parent is grandparent.left:
self._rotate_right(grandparent)
self._rotate_right(parent)
else:
self._rotate_left(grandparent)
self._rotate_left(parent)
elif node is parent.left:
# Zig-zag case: opposite directions (left child)
self._rotate_right(parent)
self._rotate_left(grandparent)
else:
# Zig-zag case: opposite directions (right child)
self._rotate_left(parent)
self._rotate_right(grandparent)
def _find_node(self, value: Any) -> SplayNode | None:
"""Find a node with the given value, splaying it to root if found."""
current = self.root
while current:
if value == current.value:
self._splay(current)
return current
elif value < current.value:
if not current.left:
break
current = current.left
else:
if not current.right:
break
current = current.right
# If we found a node (even if not exact match), splay it
if current:
self._splay(current)
return None
def search(self, value: Any) -> bool:
"""Search for a value in the tree. Returns True if found."""
return self._find_node(value) is not None
def insert(self, value: Any) -> Self:
"""Insert a value into the splay tree."""
if not self.root:
self.root = SplayNode(value)
return self
# Find the insertion point
current = self.root
while True:
if value < current.value:
if current.left:
current = current.left
else:
current.left = SplayNode(value, parent=current)
self._splay(current.left)
break
elif value > current.value:
if current.right:
current = current.right
else:
current.right = SplayNode(value, parent=current)
self._splay(current.right)
break
else:
# Value already exists, splay the existing node
self._splay(current)
break
return self
def delete(self, value: Any) -> Self:
"""Delete a value from the splay tree."""
node = self._find_node(value)
if not node:
return self
# Node to delete is now at root
left_tree = node.left
right_tree = node.right
# Remove the root
if left_tree:
left_tree.parent = None
if right_tree:
right_tree.parent = None
# If no left subtree, right becomes root
if not left_tree:
self.root = right_tree
return self
# Find the maximum in left subtree
max_left = left_tree
while max_left.right:
max_left = max_left.right
# Splay the maximum to root of left subtree
self._splay(max_left)
# Attach right subtree to the new root
max_left.right = right_tree
if right_tree:
right_tree.parent = max_left
self.root = max_left
return self
def inorder(self) -> Iterator[Any]:
"""Return an inorder iterator."""
yield from self
def preorder(self) -> Iterator[Any]:
"""Return a preorder iterator."""
def _preorder(node: SplayNode | None) -> Iterator[Any]:
if node:
yield node.value
yield from _preorder(node.left)
yield from _preorder(node.right)
yield from _preorder(self.root)
def postorder(self) -> Iterator[Any]:
"""Return a postorder iterator."""
def _postorder(node: SplayNode | None) -> Iterator[Any]:
if node:
yield from _postorder(node.left)
yield from _postorder(node.right)
yield node.value
yield from _postorder(self.root)
def get_min(self) -> Any:
"""Get the minimum value in the tree."""
if not self.root:
raise ValueError("Tree is empty")
current = self.root
while current.left:
current = current.left
self._splay(current)
return current.value
def get_max(self) -> Any:
"""Get the maximum value in the tree."""
if not self.root:
raise ValueError("Tree is empty")
current = self.root
while current.right:
current = current.right
self._splay(current)
return current.value
def is_empty(self) -> bool:
"""Check if the tree is empty."""
return self.root is None
def clear(self) -> Self:
"""Clear the tree."""
self.root = None
return self