Skip to content

Commit 2629bfc

Browse files
committed
Add recursive search and delete methods to binary tree template
1 parent 14b21f6 commit 2629bfc

File tree

5 files changed

+136
-17
lines changed

5 files changed

+136
-17
lines changed

Class10.md

-3
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,6 @@
1515
- Play with VisuAlgo's [interactive binary search tree visualization][visualgo bst]
1616

1717
### Challenges
18-
- Implement recursive tree search methods on the `BinarySearchTree` class using [binary tree starter code]:
19-
- `_find_node_recursive(item)` - return the node containing `item` in the tree, or `None` if not found (*hint: implement this first*)
20-
- `_find_parent_node_recursive(item)` - return the parent of the node containing `item` (or the parent of where `item` would be if inserted) in the tree, or `None` if the tree is empty or has only a root node
2118
- Implement tree traversal methods on the `BinarySearchTree` class using [binary tree starter code]:
2219
- `_traverse_in_order_recursive` - traverse the tree with recursive in-order traversal (DFS)
2320
- `_traverse_pre_order_recursive` - traverse the tree with recursive pre-order traversal (DFS)

Class8.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
- Read Vaidehi Joshi's [article on sets and their use in databases][BaseCS sets] with beautiful drawings and excellent examples
1010

1111
### Challenges
12-
- Implement `Set` class (backed by hash table) with the following [set operations] as instance methods and properties:
12+
- Implement `Set` class (abstract data type backed by data structure of your choice) with the following [set operations] as instance methods and properties:
1313
- `__init__(elements=None)` - initialize a new empty set structure, and add each element if a sequence is given
1414
- `size` - property that tracks the number of elements in constant time
1515
- `contains(element)` - return a boolean indicating whether `element` is in this set

Class9.md

+8-3
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,19 @@
2121
- `is_leaf` - check if the node is a leaf (an external node that has no children)
2222
- `is_branch` - check if the node is a branch (an internal node that has at least one child)
2323
- `height` - return the node's height (the number of edges on the longest downward path from the node to a descendant leaf node)
24-
- Implement `BinarySearchTree` class using `BinaryTreeNode` objects with the following properties and instance methods using [binary tree starter code]:
24+
- Implement `BinarySearchTree` class (using `BinaryTreeNode` objects) with the following properties and instance methods using [binary tree starter code]:
2525
- `root` - the tree's root node, or `None` (if the tree is empty)
2626
- `size` - property that tracks the number of nodes in constant time
2727
- `is_empty` - check if the tree is empty (has no nodes)
2828
- `height` - return the tree's height (the number of edges on the longest downward path from the tree's root node to a descendant leaf node)
2929
- `contains(item)` - return a boolean indicating whether `item` is present in the tree
3030
- `search(item)` - return an item in the tree matching the given `item`, or `None` if not found
3131
- `insert(item)` - insert the given `item` in order into the tree
32-
- `_find_node(item)` - return the node containing `item` in the tree, or `None` if not found (*hint: implement this first*)
33-
- `_find_parent_node(item)` - return the parent of the node containing `item` (or the parent of where `item` would be if inserted) in the tree, or `None` if the tree is empty or has only a root node
32+
- To simplify the `contains`, `search`, and `insert` methods with code reuse, implement iterative and recursive tree search helper methods:
33+
- `_find_node_iterative(item)` - return the node containing `item` in the tree, or `None` if not found
34+
- `_find_node_recursive(item)` - return the node containing `item` in the tree, or `None` if not found
35+
- `_find_parent_node_iterative(item)` - return the parent of the node containing `item` (or the parent of where `item` would be if inserted) in the tree, or `None` if the tree is empty or has only a root node
36+
- `_find_parent_node_recursive(item)` - return the parent of the node containing `item` (or the parent of where `item` would be if inserted) in the tree, or `None` if the tree is empty or has only a root node
3437
- Run `python binarytree.py` to test `BinarySearchTree` class instance methods on a small example
3538
- Run `pytest binarytree_test.py` to run the [binary tree unit tests] and fix any failures
3639
- Write additional unit tests for the `BinaryTreeNode` and `BinarySearchTree` classes
@@ -41,6 +44,8 @@
4144
### Stretch Challenges
4245
- Implement this additional `BinarySearchTree` class instance method:
4346
- `delete(item)` - remove `item` from the tree, if present, or else raise `ValueError` (*hint: break this down into cases based on how many children the node containing `item` has and implement helper methods for subtasks of the more complex cases*)
47+
- Write additional unit tests for the `BinarySearchTree` class
48+
- Include several test cases for the `delete` instance method covering each case handled by the algorithm
4449
- Implement binary search tree with singly linked list nodes (having only one link to another node) instead of binary tree nodes (having two links to other nodes)
4550

4651

source/binarytree.py

+63-9
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def contains(self, item):
6565
TODO: Best case running time: ??? under what conditions?
6666
TODO: Worst case running time: ??? under what conditions?"""
6767
# Find a node with the given item, if any
68-
node = self._find_node(item)
68+
node = self._find_node_recursive(item, self.root)
6969
# Return True if a node was found, or False
7070
return node is not None
7171

@@ -75,7 +75,7 @@ def search(self, item):
7575
TODO: Best case running time: ??? under what conditions?
7676
TODO: Worst case running time: ??? under what conditions?"""
7777
# Find a node with the given item, if any
78-
node = self._find_node(item)
78+
node = self._find_node_recursive(item, self.root)
7979
# TODO: Return the node's data if found, or None
8080
return node.data if ... else None
8181

@@ -91,7 +91,7 @@ def insert(self, item):
9191
self.size ...
9292
return
9393
# Find the parent node of where the given item should be inserted
94-
parent = self._find_parent_node(item)
94+
parent = self._find_parent_node_recursive(item, self.root)
9595
# TODO: Check if the given item should be inserted left of parent node
9696
if ...:
9797
# TODO: Create a new node and set the parent's left child
@@ -103,9 +103,10 @@ def insert(self, item):
103103
# TODO: Increase the tree size
104104
self.size ...
105105

106-
def _find_node(self, item):
106+
def _find_node_iterative(self, item):
107107
"""Return the node containing the given item in this binary search tree,
108-
or None if the given item is not found.
108+
or None if the given item is not found. Search is performed iteratively
109+
starting from the root node.
109110
TODO: Best case running time: ??? under what conditions?
110111
TODO: Worst case running time: ??? under what conditions?"""
111112
# Start with the root node
@@ -127,10 +128,34 @@ def _find_node(self, item):
127128
# Not found
128129
return None
129130

130-
def _find_parent_node(self, item):
131+
def _find_node_recursive(self, item, node):
132+
"""Return the node containing the given item in this binary search tree,
133+
or None if the given item is not found. Search is performed recursively
134+
starting from the given node (give the root node to start recursion).
135+
TODO: Best case running time: ??? under what conditions?
136+
TODO: Worst case running time: ??? under what conditions?"""
137+
# Check if starting node exists
138+
if node is None:
139+
# Not found (base case)
140+
return None
141+
# TODO: Check if the given item matches the node's data
142+
elif ...:
143+
# Return the found node
144+
return node
145+
# TODO: Check if the given item is less than the node's data
146+
elif ...:
147+
# TODO: Recursively descend to the node's left child, if it exists
148+
return ...
149+
# TODO: Check if the given item is greater than the node's data
150+
elif ...:
151+
# TODO: Recursively descend to the node's right child, if it exists
152+
return ...
153+
154+
def _find_parent_node_iterative(self, item):
131155
"""Return the parent node of the node containing the given item
132156
(or the parent node of where the given item would be if inserted)
133157
in this tree, or None if this tree is empty or has only a root node.
158+
Search is performed iteratively starting from the root node.
134159
TODO: Best case running time: ??? under what conditions?
135160
TODO: Worst case running time: ??? under what conditions?"""
136161
# Start with the root node and keep track of its parent
@@ -145,17 +170,46 @@ def _find_parent_node(self, item):
145170
# TODO: Check if the given item is less than the node's data
146171
elif ...:
147172
# TODO: Update the parent and descend to the node's left child
148-
parent = node
173+
parent = ...
149174
node = ...
150175
# TODO: Check if the given item is greater than the node's data
151176
elif ...:
152177
# TODO: Update the parent and descend to the node's right child
153-
parent = node
178+
parent = ...
154179
node = ...
155180
# Not found
156181
return parent
157182

158-
# This space intentionally left blank (please do not delete this comment)
183+
def _find_parent_node_recursive(self, item, node, parent=None):
184+
"""Return the parent node of the node containing the given item
185+
(or the parent node of where the given item would be if inserted)
186+
in this tree, or None if this tree is empty or has only a root node.
187+
Search is performed recursively starting from the given node
188+
(give the root node to start recursion)."""
189+
# Check if starting node exists
190+
if node is None:
191+
# Not found (base case)
192+
return None
193+
# TODO: Check if the given item matches the node's data
194+
if ...:
195+
# Return the parent of the found node
196+
return parent
197+
# TODO: Check if the given item is less than the node's data
198+
elif ...:
199+
# TODO: Recursively descend to the node's left child, if it exists
200+
return ... # Hint: Remember to update the parent parameter
201+
# TODO: Check if the given item is greater than the node's data
202+
elif ...:
203+
# TODO: Recursively descend to the node's right child, if it exists
204+
return ... # Hint: Remember to update the parent parameter
205+
206+
def delete(self, item):
207+
"""Remove given item from this tree, if present, or raise ValueError.
208+
TODO: Best case running time: ??? under what conditions?
209+
TODO: Worst case running time: ??? under what conditions?"""
210+
# TODO: Use helper methods and break this algorithm down into 3 cases
211+
# based on how many children the node containing the given item has and
212+
# implement new helper methods for subtasks of the more complex cases
159213

160214
def items_in_order(self):
161215
"""Return an in-order list of all items in this binary search tree."""

source/binarytree_test.py

+64-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,26 @@ def test_is_branch(self):
4747
node.right = None
4848
assert node.is_branch() is False
4949

50+
def test_height(self):
51+
# Create node with no children
52+
node = BinaryTreeNode(4)
53+
assert node.height() == 0
54+
# Attach left child node
55+
node.left = BinaryTreeNode(2)
56+
assert node.height() == 1
57+
# Attach right child node
58+
node.right = BinaryTreeNode(6)
59+
assert node.height() == 1
60+
# Attach left-left grandchild node
61+
node.left.left = BinaryTreeNode(1)
62+
assert node.height() == 2
63+
# Attach right-right grandchild node
64+
node.right.right = BinaryTreeNode(8)
65+
assert node.height() == 2
66+
# Attach right-right-left great-grandchild node
67+
node.right.right.left = BinaryTreeNode(7)
68+
assert node.height() == 3
69+
5070

5171
class BinarySearchTreeTest(unittest.TestCase):
5272

@@ -154,7 +174,50 @@ def test_insert_with_7_items(self):
154174
assert tree.root.right.left.data == 5
155175
assert tree.root.right.right.data == 7
156176

157-
# This space intentionally left blank (please do not delete this comment)
177+
def DISABLED_test_delete_with_3_items(self):
178+
# Create a complete binary search tree of 3 items in level-order
179+
items = [2, 1, 3]
180+
tree = BinarySearchTree(items)
181+
assert tree.root.data == 2
182+
assert tree.root.left.data == 1
183+
assert tree.root.right.data == 3
184+
# TODO: Test structure of tree after each deletion
185+
# tree.delete(2)
186+
# assert tree.root.data == ...
187+
# assert tree.root.left is ...
188+
# assert tree.root.right is ...
189+
# tree.delete(1)
190+
# assert tree.root.data == ...
191+
# assert tree.root.left is ...
192+
# assert tree.root.right is ...
193+
# tree.delete(3)
194+
# assert tree.root.data is ...
195+
# assert tree.root.left is ...
196+
# assert tree.root.right is ...
197+
198+
def DISABLED_test_delete_with_7_items(self):
199+
# Create a complete binary search tree of 7 items in level-order
200+
items = [4, 2, 6, 1, 3, 5, 7]
201+
tree = BinarySearchTree(items)
202+
# TODO: Test structure of tree after each deletion
203+
# tree.delete(4)
204+
# assert tree.root.data == ...
205+
# assert tree.root.left.data == ...
206+
# assert tree.root.right.data == ...
207+
# assert ...
208+
# assert ...
209+
# tree.delete(...)
210+
# assert tree.root.data == ...
211+
# assert tree.root.left.data == ...
212+
# assert tree.root.right.data == ...
213+
# assert ...
214+
# assert ...
215+
# tree.delete(...)
216+
# assert tree.root.data == ...
217+
# assert tree.root.left.data == ...
218+
# assert tree.root.right.data == ...
219+
# assert ...
220+
# assert ...
158221

159222
def test_items_in_order_with_3_strings(self):
160223
# Create a complete binary search tree of 3 strings in level-order

0 commit comments

Comments
 (0)