Skip to content

Commit 4ff6dee

Browse files
committed
feat: Add Splay Tree implementation to data_structures/trees/ for issue #13844
1 parent af17867 commit 4ff6dee

File tree

2 files changed

+157
-0
lines changed

2 files changed

+157
-0
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# the_algorithms/trees/splay_tree.py
2+
3+
class Node:
4+
"""A single node in the Splay Tree."""
5+
def __init__(self, key, parent=None, left=None, right=None):
6+
self.key = key
7+
self.parent = parent
8+
self.left = left
9+
self.right = right
10+
11+
class SplayTree:
12+
"""
13+
A self-adjusting Binary Search Tree (BST) that uses the splay operation
14+
to move the most recently accessed node to the root of the tree.
15+
"""
16+
def __init__(self):
17+
self.root = None
18+
19+
def _rotate(self, x: Node):
20+
"""Performs a single rotation (left or right) around node x."""
21+
p = x.parent # Parent of x
22+
g = p.parent # Grandparent of x
23+
24+
if p.left == x: # Right rotation (x is left child)
25+
p.left = x.right
26+
if x.right:
27+
x.right.parent = p
28+
x.right = p
29+
else: # Left rotation (x is right child)
30+
p.right = x.left
31+
if x.left:
32+
x.left.parent = p
33+
x.left = p
34+
35+
# Update parent pointers
36+
p.parent = x
37+
x.parent = g
38+
39+
# Update grandparent pointer to x
40+
if g:
41+
if g.left == p:
42+
g.left = x
43+
else:
44+
g.right = x
45+
else:
46+
self.root = x # x is the new root
47+
48+
def _splay(self, x: Node):
49+
"""Moves node x to the root of the tree using zig, zig-zig, or zig-zag operations."""
50+
while x.parent:
51+
p = x.parent
52+
g = p.parent
53+
54+
if not g:
55+
# Zig operation (p is the root)
56+
self._rotate(x)
57+
elif (p.left == x and g.left == p) or (p.right == x and g.right == p):
58+
# Zig-zig operation (x, p, g are all on the left or all on the right)
59+
self._rotate(p) # Rotate p first
60+
self._rotate(x) # Then rotate x
61+
else:
62+
# Zig-zag operation (x is left/right and p is right/left)
63+
self._rotate(x) # Rotate x first
64+
self._rotate(x) # Then rotate x again
65+
66+
def search(self, key):
67+
"""
68+
Searches for a node with the given key. If found, the node is splayed to the root.
69+
If not found, the last accessed node (parent of where the key would be) is splayed.
70+
Returns the node if found, otherwise None.
71+
"""
72+
curr = self.root
73+
last = None # Keeps track of the last node accessed
74+
75+
while curr:
76+
last = curr
77+
if key == curr.key:
78+
self._splay(curr)
79+
return curr
80+
elif key < curr.key:
81+
curr = curr.left
82+
else:
83+
curr = curr.right
84+
85+
if last:
86+
self._splay(last) # Splay the last accessed node if key was not found
87+
return None
88+
89+
def insert(self, key):
90+
"""Inserts a new key and then splays it to the root."""
91+
if not self.root:
92+
self.root = Node(key)
93+
return
94+
95+
# Regular BST insertion
96+
curr = self.root
97+
parent = None
98+
while curr:
99+
parent = curr
100+
if key < curr.key:
101+
curr = curr.left
102+
elif key > curr.key:
103+
curr = curr.right
104+
else: # Key already exists, splay it and return (or update value)
105+
self._splay(curr)
106+
return
107+
108+
new_node = Node(key, parent=parent)
109+
if key < parent.key:
110+
parent.left = new_node
111+
else:
112+
parent.right = new_node
113+
114+
self._splay(new_node)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# the_algorithms/trees/splay_tree_test.py
2+
3+
import unittest
4+
from splay_tree import SplayTree
5+
6+
class TestSplayTree(unittest.TestCase):
7+
def test_insert_and_root(self):
8+
"""Test basic insertion and verify the splayed node becomes the root."""
9+
tree = SplayTree()
10+
keys = [50, 30, 70, 20, 40]
11+
for key in keys:
12+
tree.insert(key)
13+
self.assertEqual(tree.root.key, key, f"Expected {key} to be the root after insertion.")
14+
15+
def test_search_and_splay(self):
16+
"""Test searching for an existing key and verify it is splayed to the root."""
17+
tree = SplayTree()
18+
keys = [50, 30, 70, 20, 40, 60, 80]
19+
for key in keys:
20+
tree.insert(key)
21+
22+
# Search for 20. It should become the new root.
23+
found_node = tree.search(20)
24+
self.assertIsNotNone(found_node)
25+
self.assertEqual(found_node.key, 20)
26+
self.assertEqual(tree.root.key, 20, "20 should be the root after search.")
27+
28+
# Search for a key that doesn't exist (99). The last accessed node (e.g., 80) should be splayed.
29+
_ = tree.search(99)
30+
# The exact last accessed node depends on the tree structure, but it should not be the original root (50)
31+
self.assertNotEqual(tree.root.key, 50, "Root should change after unsuccessful search.")
32+
33+
def test_empty_tree(self):
34+
"""Test operations on an empty tree."""
35+
tree = SplayTree()
36+
self.assertIsNone(tree.search(10))
37+
self.assertIsNone(tree.root)
38+
39+
tree.insert(10)
40+
self.assertEqual(tree.root.key, 10)
41+
42+
if __name__ == '__main__':
43+
unittest.main()

0 commit comments

Comments
 (0)