Skip to content

Commit abc5654

Browse files
committed
feat(datastructures, trees): threaded bst
1 parent 15d069e commit abc5654

12 files changed

Lines changed: 190 additions & 19 deletions

File tree

datastructures/trees/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from datastructures.trees.tree import Tree, TreeNode, T
1+
from datastructures.trees.tree import Tree, TreeNode
22
from datastructures.trees.binary import BinaryTree, BinaryTreeNode
3+
from datastructures.trees.types import T
34

4-
__all__ = ["Tree", "TreeNode", "BinaryTree", "BinaryTreeNode"]
5+
__all__ = ["Tree", "TreeNode", "BinaryTree", "BinaryTreeNode", "T"]

datastructures/trees/binary/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
longest_uni_value_path,
66
)
77
from datastructures.trees.binary.node import BinaryTreeNode
8+
from datastructures.trees.binary.threaded import ThreadedBinarySearchTree
9+
from datastructures.trees.binary.search_tree import BinarySearchTree
810
from datastructures.trees.binary.utils import (
911
lowest_common_ancestor,
1012
lowest_common_ancestor_ptr,
@@ -24,4 +26,6 @@
2426
"connect_all_siblings",
2527
"connect_all_siblings_ptr",
2628
"mirror_binary_tree",
29+
"ThreadedBinarySearchTree",
30+
"BinarySearchTree",
2731
]

datastructures/trees/binary/node.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Optional, List, Any
22

3-
from datastructures.trees.node import TreeNode, T
3+
from datastructures.trees.node import TreeNode
4+
from datastructures.trees.types import T
45

56

67
class BinaryTreeNode(TreeNode):
@@ -21,6 +22,8 @@ def __init__(
2122
depth: Optional[int] = None,
2223
height: Optional[int] = None,
2324
degree: Optional[int] = None,
25+
left_thread: Optional[bool] = False,
26+
right_thread: Optional[bool] = False,
2427
) -> None:
2528
"""
2629
Constructor for BinaryTreeNode class. This will create a new node with the provided data and optional
@@ -38,7 +41,16 @@ def __init__(
3841
of the tree, then this is the next node on the next level starting from the left. If this is the last node
3942
in the tree, then this is None.
4043
"""
41-
super().__init__(data, key, parent, depth, height, degree)
44+
super().__init__(
45+
data,
46+
key,
47+
parent,
48+
depth,
49+
height,
50+
degree,
51+
left_thread=left_thread,
52+
right_thread=right_thread,
53+
)
4254
self.left: Optional[BinaryTreeNode] = left
4355
self.right: Optional[BinaryTreeNode] = right
4456
# Next is a pointer that connects this node to it's right sibling in the tree. If this node is the right most
@@ -188,11 +200,6 @@ def children(self) -> List["BinaryTreeNode"] | None:
188200
if not self.left and not self.right:
189201
return []
190202

191-
@property
192-
def height(self) -> int:
193-
"""Height of a node is the number of edges from this node to the deepest node"""
194-
pass
195-
196203
def __repr__(self):
197204
parent_data = self.parent.data if self.parent else None
198205
next_data = self.next.data if self.next else None

datastructures/trees/binary/search_tree/binary_search_tree.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
from datastructures.queues.fifo import FifoQueue
55
from datastructures.stacks.dynamic import DynamicSizeStack
6-
from datastructures.trees import T
76
from datastructures.trees.binary.tree import BinaryTree
7+
from datastructures.trees.types import T
88

99

1010
class BinarySearchTree(BinaryTree):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Threaded Binary Tree
2+
3+
This contains code that represents a threaded binary tree
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from datastructures.trees.binary.threaded.threaded_binary_search_tree import (
2+
ThreadedBinarySearchTree,
3+
)
4+
5+
6+
__all__ = ["ThreadedBinarySearchTree"]
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import unittest
2+
from typing import List
3+
from parameterized import parameterized
4+
from utils.test_utils import custom_test_name_func
5+
from datastructures.trees.binary.threaded.threaded_binary_search_tree import (
6+
ThreadedBinarySearchTree,
7+
)
8+
9+
THREADED_BST_INORDER_TESTCASES = [([4, 2, 6, 1, 3, 5, 7], [1, 2, 3, 4, 5, 6, 7])]
10+
11+
THREADED_BST_REVERSE_INORDER_TESTCASES = [
12+
([4, 2, 6, 1, 3, 5, 7], [7, 6, 5, 4, 3, 2, 1])
13+
]
14+
15+
16+
class ThreadedBinarySearchTreeInorderTraversalTestCase(unittest.TestCase):
17+
@parameterized.expand(
18+
THREADED_BST_INORDER_TESTCASES, name_func=custom_test_name_func
19+
)
20+
def test_inorder_traversal(self, values: List[int], expected: List[int]):
21+
tree = ThreadedBinarySearchTree()
22+
for value in values:
23+
tree.insert_node(value)
24+
25+
actual = tree.inorder_traversal()
26+
self.assertEqual(expected, actual)
27+
28+
@parameterized.expand(
29+
THREADED_BST_REVERSE_INORDER_TESTCASES, name_func=custom_test_name_func
30+
)
31+
def test_reverse_inorder_traversal(self, values: List[int], expected: List[int]):
32+
tree = ThreadedBinarySearchTree()
33+
for value in values:
34+
tree.insert_node(value)
35+
36+
actual = tree.reverse_inorder_traversal()
37+
self.assertEqual(expected, actual)
38+
39+
40+
if __name__ == "__main__":
41+
unittest.main()
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from typing import Optional, List
2+
from datastructures.trees.binary.search_tree import BinarySearchTree
3+
from datastructures.trees.binary.node import BinaryTreeNode
4+
from datastructures.trees.types import T
5+
6+
7+
class ThreadedBinarySearchTree(BinarySearchTree):
8+
"""
9+
A fully (double-)threaded binary search tree.
10+
11+
Every node's left/right pointer is EITHER a real child link OR a "thread" -
12+
a shortcut straight to that node's inorder predecessor / successor. Which
13+
one it is gets tracked with a boolean flag per side. This lets us traverse
14+
the whole tree in sorted order with O(1) extra space: no recursion, no
15+
explicit stack.
16+
"""
17+
18+
def insert_node(self, value: Optional[T]) -> None:
19+
if self.root is None:
20+
self.root = BinaryTreeNode(value)
21+
return None
22+
current = self.root
23+
24+
while True:
25+
# No duplicates
26+
if value == current.data:
27+
return None
28+
if value < current.data:
29+
if not current.left_thread and current.left is not None:
30+
current = current.left
31+
continue
32+
33+
new_node = BinaryTreeNode(value)
34+
new_node.left = current.left # inherit current's predecessor
35+
new_node.left_thread = True
36+
new_node.right = current # current is the new successor
37+
new_node.right_thread = True
38+
current.left = new_node
39+
current.left_thread = False
40+
return None
41+
else:
42+
if not current.right_thread and current.right is not None:
43+
current = current.right
44+
continue
45+
new_node = BinaryTreeNode(value)
46+
new_node.right = current.right # inherit current's successor
47+
new_node.right_thread = True
48+
new_node.left = current # current is the new predecessor
49+
new_node.left_thread = True
50+
current.right = new_node
51+
current.right_thread = False
52+
return None
53+
54+
@staticmethod
55+
def _leftmost(node: Optional[BinaryTreeNode]) -> Optional[BinaryTreeNode]:
56+
if node is None:
57+
return None
58+
while not node.left_thread and node.left is not None:
59+
node = node.left
60+
return node
61+
62+
@staticmethod
63+
def _rightmost(node: Optional[BinaryTreeNode]) -> Optional[BinaryTreeNode]:
64+
if node is None:
65+
return None
66+
while not node.right_thread and node.right is not None:
67+
node = node.right
68+
return node
69+
70+
def successor(self, node: BinaryTreeNode) -> Optional[BinaryTreeNode]:
71+
if node.right_thread:
72+
return node.right
73+
return self._leftmost(node.right)
74+
75+
def predecessor(self, node: BinaryTreeNode) -> Optional[BinaryTreeNode]:
76+
if node.left_thread:
77+
return node.left
78+
return self._rightmost(node.left)
79+
80+
def inorder_traversal(self) -> List[T]:
81+
"""Iterative inorder traversal using threads only - no stack needed."""
82+
result = []
83+
cur = self._leftmost(self.root)
84+
while cur is not None:
85+
result.append(cur.data)
86+
cur = self.successor(cur)
87+
return result
88+
89+
def reverse_inorder_traversal(self) -> List[T]:
90+
"""Same idea, walking predecessor threads right-to-left."""
91+
result = []
92+
cur = self._rightmost(self.root)
93+
while cur is not None:
94+
result.append(cur.data)
95+
cur = self.predecessor(cur)
96+
return result

datastructures/trees/binary/tree/binary_tree.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
from itertools import chain
55

66
from datastructures.stacks.dynamic import DynamicSizeStack
7-
from datastructures.trees.tree import Tree, T
7+
from datastructures.trees.tree import Tree
8+
from datastructures.trees.types import T
89
from datastructures.trees.node import TreeNode
910
from datastructures.trees.binary.node import BinaryTreeNode
1011
from datastructures.trees.binary.tree.binary_tree_utils import longest_uni_value_path
@@ -66,7 +67,7 @@ def height_helper(current_node: BinaryTreeNode) -> int:
6667
def has_next(self) -> bool:
6768
pass
6869

69-
def increasing_order_traversal(self) -> TreeNode:
70+
def increasing_order_traversal(self) -> BinaryTreeNode:
7071
pass
7172

7273
def get_depth(self) -> int:

datastructures/trees/node.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
from typing import Generic, TypeVar, Any, Optional
2-
3-
T = TypeVar("T", bound=Any)
1+
from typing import Generic, Any, Optional
2+
from datastructures.trees.types import T
43

54

65
class TreeNode(Generic[T]):
@@ -20,6 +19,8 @@ def __init__(
2019
depth: Optional[int] = None,
2120
height: Optional[int] = None,
2221
degree: Optional[int] = None,
22+
left_thread: Optional[bool] = False,
23+
right_thread: Optional[bool] = False,
2324
):
2425
"""
2526
Initialises a tree node with a value, key and a parent node.
@@ -32,6 +33,10 @@ def __init__(
3233
depth (int): The depth of the current node.
3334
height (int): The height of the current node.
3435
degree (int): The degree of the current node.
36+
left_thread (bool): Whether this node has a thread pointer to its inorder predecessor. Defaults to False. If
37+
True, the pointer is a thread (predecessor). False indicates the pointer is a real child link or None
38+
right_thread (bool): Whether this node has a thread pointer to its successor. Defaulted to False. If True, the
39+
pointer is a thread to the inorder successor.
3540
3641
Notes:
3742
The key is used to identify the node in the tree. If not key is provided, a hash of the data is used.
@@ -42,9 +47,14 @@ def __init__(
4247
self.depth = depth
4348
self.height = height
4449
self.degree = degree
50+
self.left_thread = left_thread
51+
self.right_thread = right_thread
4552

4653
def __repr__(self):
47-
return f"TreeNode(data={self.data}, key={self.key}, depth={self.depth}, height={self.height}, degree={self.degree})"
54+
return (
55+
f"TreeNode(data={self.data}, key={self.key}, depth={self.depth}, height={self.height}, degree={self.degree}, "
56+
f"left_thread={self.left_thread}, right_thread={self.right_thread})"
57+
)
4858

4959
def __eq__(self, other: "TreeNode[T]") -> bool:
5060
"""Checks if this node is equal to another node based on the data they contain

0 commit comments

Comments
 (0)