Removing Root Node in Binary Search Tree - python

I'm currently unable to delete the root node of my binary search tree in these situations
the root node doesn't have a left attribute or doesn't have a right attribute
the root has neither attribute (leaf)
from __future__ import annotations
from collections.abc import Iterator
from typing import Any, Optional
class TreeNode:
def __init__(self, value: Any, left: BST, right: BST):
self.value = value
self.left = left
self.right = right
def __repr__(self):
return f"TreeNode({self.value}, {self.left}, {self.right})"
def __eq__(self, other):
return self.value == other.value and \
self.right == other.right and self.left == other.left
BST = Optional[TreeNode]
def is_empty(tree: BST) -> bool:
"""Return True if the tree is empty, False otherwise."""
return tree is None # check if BST input is none (empty)
def search(tree: BST, value: Any) -> bool:
"""Return True if value is in tree, False otherwise."""
if tree is None:
return False
else:
return (
tree.value == value
or search(tree.left, value)
or search(tree.right, value)
)
def insert(tree: BST, value: Any) -> BST:
"""Insert the value into the tree in the proper location."""
if tree is None:
return TreeNode(value, None, None)
elif tree.value is None:
tree.value = value
elif value > tree.value:
if tree.right is None:
tree.right = TreeNode(value, None, None)
return insert(tree.right, value)
elif value < tree.value:
if tree.left is None:
tree.left = TreeNode(value, None, None)
return insert(tree.left, value)
def delete(tree: BST, value: Any) -> BST:
"""Remove the value from the tree (if present).
If the value is not present, this function does nothing.
"""
try:
root = (tree.value,)
print(root[0])
finally:
pass
if tree is None:
return tree
elif value < tree.value:
tree.left = delete(tree.left, value)
elif value > tree.value:
tree.right = delete(tree.right, value)
else:
if tree.left is None and tree.right is None: # No children
if tree.value == root[0]:
tree.value = None
return tree
elif tree.left is not None and tree.right is not None: # Two children
replacement = tree.right
while replacement.left is not None: # Find smallest "big" value
replacement = replacement.left
tree.value = replacement.value # Set value to smallest "big" value
tree.right = delete(tree.right, replacement.value) # Delete value
else: # One child
if tree.left is not None: # Promote left child
return tree.left
else:
return tree.left
return tree
I'm struggling to delete the root of my BST when one of its left/right components is empty or if both components are empty. Did you guys keep track of your root value throughout your function? I tried implementing a tuple to save keep my root Node, however, my implementation is still running into errors. 

First of all, you have some code that gives a special meaning to the value None in the root node. This is not how it should be done. Your is_empty function is right: a tree is empty only when the tree reference itself is None, not when the root's value happens to be None.
So remove this logic (concerning tree.value is None) from your insert and delete functions. And in your main program, initialise your tree as None, not as TreeNode(None).
Insert
There are some bugs in insert:
In the value > tree.value block, when tree.right is None, your code creates a new node, and then still makes a recursive call to insert a node. This is not right. In fact, you should not create a node here at all, but completely rely on the recursive call, which will do that when it gets a None as argument.
In this same block, you should not return what insert() returns, which is right subtree. Instead you should assign that return value to tree.right and then return tree
In case none of the if blocks is entered (which happens when the value is already in the tree), you should still return tree.
Here is a correction for insert:
def insert(tree: BST, value: Any) -> BST:
"""Insert the value into the tree in the proper location."""
if tree is None:
return TreeNode(value, None, None)
if value > tree.value:
tree.right = insert(tree.right, value) # correction
elif value < tree.value:
tree.left = insert(tree.left, value) # correction
return tree # missing: must always return BST type
Delete
There are these issues in the delete function:
There is a trivial bug near the end of the function where you do return tree.left in both cases. One of those should be return tree.right.
In the case the node is a leaf you should not return tree, but None as that will serve for the caller to actually detach that node.
Not a problem, but the case where there are no children does not need a separate treatment. The code that deals with the 1-child case can also deal with the 0-children case.
Here is a correction for delete:
def delete(tree: BST, value: Any) -> BST:
"""Remove the value from the tree (if present).
If the value is not present, this function does nothing.
"""
if tree is None:
return tree
elif value < tree.value:
tree.left = delete(tree.left, value)
elif value > tree.value:
tree.right = delete(tree.right, value)
elif tree.left and tree.right: # Has two children
replacement = tree.right
while replacement.left is not None: # Find smallest "big" value
replacement = replacement.left
tree.value = replacement.value # Set value to smallest "big" value
tree.right = delete(tree.right, replacement.value) # Delete value
else: # Zero or one child (same treatment)
return tree.left or tree.right # shorter notation to promote a child
return tree
Main program
There was no main program included in your code, so I will provide one here. It includes:
A utility function to display a tree in a 90° rotated way (with root pictured at the left side), using indentation to indicate depth.
Code that populates a tree.
Note that the tree starts as None (empty) and that the return value of insert is always assigned back to tree.
Code that deletes some nodes from this tree.
Note again that the return value of delete is always assigned back to tree.
def display(tree: BST, tab: str=""):
if tree:
display(tree.right, tab + " ")
print(tab, tree.value)
display(tree.left, tab + " ")
tree: BST = None # This is an empty tree
for value in (4, 2, 6, 1, 3, 5, 7):
tree = insert(tree, value)
print("populated BST:")
display(tree)
for value in (6,9,1,4):
print("delete", value)
tree = delete(tree, value)
display(tree)
This demo program will produce this output:
populated BST:
7
6
5
4
3
2
1
delete 6
7
5
4
3
2
1
delete 9
7
5
4
3
2
1
delete 1
7
5
4
3
2
delete 4
7
5
3
2

Related

Confusion about recursion in a BST python

I am trying to understand the recursive call within the binary function is_bst. The goal of the function is to check if a tree is a binary search tree or not. Note, This is just an excerpt from the complete function. I am trying to understand what is happening on this block of code is_bst_l, min_l, max_l = is_bst(node.left) in the is_bst function. The is_bst function return (True, None, None), I am trying to figure out how the function came up with the return value by going through the function recursively. The is_bst function takes as input the binary tree node from the parse_tuple function. As an example, when I try to unpack this line of code is_bst_l, min_l, max_l = node.left outside the is_bst function I get TypeError: cannot unpack non-iterable TreeNode object, but within the function, I don't get an error. My Question is
what is happening recursively within the is_bst function.
How can I don't get the unpack TypeError within the is_bst function.
`
#Tree Class
class TreeNode:
def __init__(self, key):
self.key = key
self.left = None
self.right = None
#function converts a turble to a binary tree
def parse_tuple(data):
# print(data)
if isinstance(data, tuple) and len(data) == 3:
node = TreeNode(data[1])
node.left = parse_tuple(data[0])
node.right = parse_tuple(data[2])
elif data is None:
node = None
else:
node = TreeNode(data)
return node
Function Checks if a tree is a Binary search tree or not.
def remove_none(nums):
return [x for x in nums if x is not None]
def is_bst(node):
if node is None:
return True, None, None
is_bst_l, min_l, max_l = is_bst(node.left)
is_bst_r, min_r, max_r = is_bst(node.right)
is_bst_node = (is_bst_l and is_bst_r and
(max_l is None or node.key > max_l) and
(min_r is None or node.key < min_r))
# print('Minkey left: ', min_l, 'Nodekey :', node.key, 'Maxkey left: ',min_r)
# print('Minkey left: ', max_l, 'Nodekey :', node.key, 'Maxkey left: ',max_r)
min_key = min(remove_none([min_l, node.key, min_r]))
max_key = max(remove_none([max_l, node.key, max_r]))
# print(node.key, min_key, max_key, is_bst_node)
return is_bst_node, min_key, max_key
#function call
my_tuple= ((None,3,None),2,(None,5,None))
node = parse_tuple(my_tuple)
The function is returning the maximum and minimum key on the left and right of the current node (in addition to the True/False result of the check). Its recursion assembles these keys / validity states from the left and right nodes creating the need to manage (i.e. exclude) None results from the subnodes returned value.
This is overly complex, and probably not worth your time to analyze and understand.
I think you'll find this one a bit more straightforward:
def is_bst(node,minKey=None,maxKey=None):
if node is None: return True
if minKey is not None and node.key<=minKey: return False
if maxKey is not None and node.key>=maxKey: return False
if not is_bst(node.left,minKey,self.key): return False
if not is_bst(node.right,self.key,maxKey): return False
return True
A tree is a BST if recursively for every node:
Its left tree if it exists is a BST
Its right tree if it exists is a BST
The largest value of the left tree, if it exists, must be smaller than the node's value
The smallest value of the right tree, if it exists, must be larger than the node's value
Hence it makes sense for the return value to the the tuple of three values:
Am I a BST?
What is the smallest node in my subtree
What is the largest node in my subtree.
When is_bst(node.left) or is_bst(node.right) returns "None" for max_l,_min_l or max_r,min_r the following hapens:
condition = (max_l is None) or (node.key > max_l)
condition1 : (max_l is None)
condition2 : (node.key >max_l)
Case1:
is_bst_l, min_l, max_l = is_bst(True,None,None)
condition = condition1 or condition2
condition = True or condition2 will not be processed since condition1 is "False"
condition = True because of condition1
Case2:
is_bst_l, min_l, max_l = is_bst(True,1,1)
condition = condition1 or condition2
condition = False or condition2 will be processed since condition1 is not "True"
condition = True because of condition2

adding a node in Binary Search Tree

I am currently working on implementing a Binary search tree but I have a problem with creating the add_node() function. I tried to make it in a recursive way, but the result was weird.
For example- If I run add_node(1), add_node(5), and add_node(0), my tree has only 1, no 5 and 0.
I would be happy if you tell me what the problem is.
def add_node(self, value: int) -> None:
if self.root == None:
self.root = Node(value)
return
else:
return self.add_recursion(self.root, value)
def add_recursion(self, node: Node, value: int) -> None:
if node == None:
node = Node(value)
return
elif value < node.value:
return self.add_recursion(node.left, value)
else:
return self.add_recursion(node.right, value)
When a None value is passed into a function, it is passed by value, not by reference, since... there is no reference.
elif value < node.value:
return self.add_recursion(node.left, value)
else:
return self.add_recursion(node.right, value)
When node.left or node.right is None, a Node ends up being created but not attached to node.
So what you could do is handle the cases where they are None separately.
def add_recursion(self, node: Node, value: int) -> None:
if node == None:
node = Node(value)
elif value < node.value:
if node.left == None:
node.left = Node(value)
else:
self.add_recursion(node.left, value)
else:
if node.right == None:
node.right = Node(value)
else:
self.add_recursion(node.right, value)
While this is workable, it becomes quite ugly. Look at the answer by Locke to see a better way to structure your code.
Also, an additional tip for readability, avoid using return where they aren't necessary such as at the end of a flow.
The issue is how you handle if node.left or node.right does not exist. Since the arguments are copied for each call, setting the value of node in add_recursion has no effect on the tree.
def foo(val):
print("foo start:", val)
val = 5
print("foo end:", val)
bar = 3
foo(bar) # Value of bar is copied for call
print("after foo:", bar) # Prints bar is still 3
I think you might also be getting confused due to how the root node is handled. Here is some starter code on how can handle the initial call to add_recursive.
class Node:
def __init__(self, value: int):
self.value = value
self.left = None
self.right = None
def add_recursive(self, value: int):
# TODO: recursively add to left or right
# For example, here is how you could recursively make a linked list
if self.right is None:
self.right = Node(value)
else:
self.right.add_recursive(value)
class BinarySearchTree:
def __init__(self):
self.root = None
def add_node(self, value: int):
if self.root is None:
self.root = Node(value)
else:
# Call add_recursive on root instead of with root.
self.root.add_recursive(value)
tree = BinarySearchTree()
tree.add_node(1)
tree.add_node(5)
tree.add_node(0)

Pytest testing for class value

Doing a basic implementation of a binary tree in python and testing the implementation using pytest. One of the unit test is to check if the new node is inserted properly to the left (or the right) of the parent node. When I do the assert statement is shows the correct comparison on the left and on the right of the == but tails me the test failed. Snippets of code is attached.
Relevant part of the main code:
class Node:
# Contains data variables.
def __init__(self, val: Optional[int]=None) -> None:
self.value: Optional[int] = val
self.left: Optional[Node] = None
self.right: Optional[Node] = None
def __repr__(self):
return f"{self.value}"
def insert(self, data: int) -> bool:
# Insert a node to the right or left of a parent node.
# Return False only if trying to insert duplicate.
if self.value == data: # Duplicate found
return False
if self.value > data: # New node to the left of parent node
if self.left: # If left exist, then recurse
return self.left.insert(data)
self.left = Node(data) # Default, create a new left node
return True
else:
if self.right: # If right exist, then recurse
return self.right.insert(data)
self.right = Node(data) # Default, create a new right node
return True
Specific pytest unit test
def test_node_insert_left():
node = Node(4)
node.insert(3)
#print(f"\n{node.left}-{node}-{node.right}")
assert node.value == 4
assert node.left == Node(3) # I've also tried node.left == 3
assert type(node.left) == type(Node())
assert node.right == None
The error message
> assert node.left == Node(3)
E assert 3 == 3
E -3
E +3
Two nodes will not equal unless you implement __eq__ in your node class.
You could write an __eq__ method in Node:
def __eq__(self, other):
if not isinstance(other, Node):
return NotImplemented
return self.value==other.value
if you want to regard any two nodes holding the same value as equal.
Then assert node.left == Node(3) would work.
However, sinces nodes contain more than just a value, that might not be ideal.
More simply:
For your assertion, what you actually want to test is that the node holds the value 3. For that, you could simply change your assertion to
assert node.left.value == 3

Deleting leftmost leaf in a binary tree

I wish to delete the leftmost leaf in a BT( not BST!). I tried to delete it using Reverse level order traversal but once I find the leftmost node and delete it and try returning, I cannot handle the extra recursive calls. Basically with the current code, my entire tree gets deleted recursively, but I just want it to delete leftmost leaf.
I tried breaking the recursion using exit() and my entire program stopped.
On first call only 5 should be deleted. On next call 25 should be deleted . On next call 66 should be deleted.( basically swimming up)
This is what my code looks like:
def del_leaf(node):
if(root==None):
return
if root:
# First recur on left child
del_leaf(root.left)
# the recur on right child
del_leaf(root.right)
# now print the data of node
if ( (root.isvisited == False)):
root.isvisited = True
root.left = None
root.right = None
return
The function needs a return value, something that tells the caller:
this node does not exist
this node is childless so you can delete it
OK, we're done.
So, something like
def del_leaf(node):
if(root==None):
return 1
left = del_leaf(root.left)
if (left == 2):
root.left = None
return 3
if (left == 3):
return 3
right = del_leaf(root.right)
if (right == 2):
root.right = None
return 3
if (right == 3):
return 3
return 2
I haven't tested it; that's left as an exercise for you. Also left:
better enum names than 1, 2, and 3
support for an arbitrary number of child nodes
support for an ordering function
I have modified the program that does not use recursion to:
Not rely on garbage collection to print out when a node has been removed.
Build the entire tree as presented by the OP.
Note: When the program descends down the left subtree and finds that the leftmost leaf has a right subtree, it removes this subtree. This program does not descend down the right subtree to delete the leftmost leaf of that subtree. The second algorthm does. Take your pick.
class Node:
def __init__(self, value):
self.value = value
# build tree
root = Node(90)
# left subtree:
node_50 = Node(50)
node_20 = Node(20)
node_75 = Node(75)
node_5 = Node(5)
node_5.left = None
node_5.right = None
node_25 = Node(25)
node_25.left = None
node_25.right = None
node_66 = Node(66)
node_66.left = None
node_66.right = None
node_80 = Node(80)
node_80.left = None
node_80.right = None
root.left = node_50
node_50.left = node_20
node_50.right = node_75
node_20.left = node_5
node_20.right = node_25
node_75.left = node_66
node_75.right = node_80
# right subtree:
node_150 = Node(150)
node_95 = Node(95)
node_175 = Node(175)
node_92 = Node(92)
node_92.left = None
node_92.right = None
node_111 = Node(111)
node_111.left = None
node_111.right = None
node_166 = Node(166)
node_166.left = None
node_166.right = None
node_200 = Node(200)
node_200.left = None
node_200.right = None
root.right = node_150
node_150.left = node_95
node_150.right = node_175
node_95.left = node_92
node_95.right = node_111
node_175.left = node_166
node_175.right = node_200
def del_leftmost_leaf(root):
if root.left is None and root.right is None:
print(f'Removing {root.value}')
return None # root has changed to None
parent = root
if root.left is None:
node = root.right
else:
node = root.left
while node.left:
parent = node
node = node.left
if node.right is None:
print(f'Removing {node.value}')
if parent.left is None:
parent.right = None
else:
parent.left = None
else:
print(f'Removing {node.right.value}')
node.right = None
return root # root hasn't changed
while root:
root = del_leftmost_leaf(root)
Prints:
Removing 5
Removing 25
Removing 20
Removing 75
Removing 50
Removing 92
Removing 111
Removing 95
Removing 175
Removing 150
Removing 90
See demo
Second Algortihm
def del_leftmost_leaf(root):
if root.left is None and root.right is None:
print(f'Removing {root.value}')
return None # root has changed to None
parent = root
if root.left is None:
node = root.right
else:
node = root.left
while node.left:
parent = node
node = node.left
if node.right is None:
print(f'Removing {node.value}')
if parent.left is None:
parent.right = None
else:
parent.left = None
else:
node.right = del_leftmost_leaf(node.right)
return root # root hasn't changed
while root:
root = del_leftmost_leaf(root)
Prints:
Removing 5
Removing 25
Removing 20
Removing 66
Removing 80
Removing 75
Removing 50
Removing 92
Removing 111
Removing 95
Removing 166
Removing 200
Removing 175
Removing 150
Removing 90
See demo
If you only actually to remove the leftmost leaf node, a simple recursion can handle this problem pretty easily, but you'll need to use a return value from the function to communicate information back up the call stack. I suggest we return True if the current node is the leaf we're searching for, which should be deleted by its parent, and False otherwise:
def del_leaf(node):
if node is None:
raise ValueError("Can't remove a leaf node from an empty tree")
if node.left: # first recursive case
if del_leaf(node.left): # if the recursion returns True, our immediate child is a leaf
node.left = None # so delete it
return False
elif node.right: # second recursive case
if del_leaf(node.right):
node.right = None
return False
else: # base case, we're the leaf!
return True
Note that a function like this cannot ever delete the root value of a one-element tree, because it don't own the reference to that root node (that's probably in a variable in the calling scope). What it will do is return True to the caller in that case, so the calling code can delete the root reference itself, if necessary.
However, if you're really looking to remove the furthest leaf node from the root, with a preference for the leftmost if there are several leaves at the same depth, then you need a somewhat more sophisticated algorithm.
One way to do it that might be pretty efficient is to make sure each node keeps its height as an attribute. Then we can easily tell which branch of the tree we need to follow to find the leaf we want to remove.
# assume the nodes have a height attribute, leaf nodes have height 0
def update_height(node): # a helper function
node.height = 1 + max(node.left.height if node.left is not None else -1,
node.right.height if node.right is not None else -1)
# use this to initially set up heights (if you don't build them into the tree structure)
def setup_heights(node):
if node is None:
return
setup_heights(node.left)
setup_heights(node.right)
update_height(node)
def del_leaf(node):
if node is None:
raise ValueError("Can't remove from an empty tree")
if node.height == 0:
return True # the root needs to be removed by the caller
if self.height == 1: # we're the parent of a leaf, so delete one of our child nodes
if self.left is not None:
self.left = None
else:
self.right = None
elif self.left is not None and (self.right is None or
self.left.height >= self.right.height):
del_leaf(self.left)
else:
del_leaf(self.right)
update_height(node)
return False

How to overwrite (or pass) parameters in a recursive function?

I'm searching for a way to overwrite parameters in a recursive function.
I was writing the code to check whether or not it's a binary search tree and below is the code I wrote.
class Node:
# Constructor to create a new node
def __init__(self, data):
self.data = data
self.left = None
self.right = None
def isItBST(root, prev_value, curr_value):
"""
Using in order traversal, we check if every node is sorted because if it's a BST,
then it should be sorted.
"""
if root is not None:
isItBST(root.left, prev_value, curr_value)
curr_value = root.data
print root.data, "curr: ",curr_value, "prev_value: ",prev_value
if (prev_value > curr_value ):
print "wrong!" # return False
prev_value = curr_value
isItBST(root.right, prev_value, curr_value)
# Define a tree
root = Node(4)
root.left = Node(2)
root.right = Node(5)
root.left.left = Node(1)
root.left.right = Node(8) # This node violates the BT rule
if (isItBST(root,-999999,root.data))==False:
print "Is Not BST"
else:
print "Is a BST"
output:
## -- End pasted text --
1 curr: 1 prev_value: -999999
2 curr: 2 prev_value: -999999
8 curr: 8 prev_value: 2
4 curr: 4 prev_value: -999999
5 curr: 5 prev_value: 4
Is a BST <== which is WRONG
If you run this code, it fails to detect because the parameter prev_value of the root doesn't get updated during the recursion. Desirably, I'd like to update the prev_value during the recursion so that it could print "wrong!" when the Node(8) violates the BT rule.
Your answer doesn't need to be specific to this problem. I would appreciate any kind of general idea to pass parameters in recursion.
The problem is that you're never returning True or False to indicate whether it's a BST.
def isItBST(root, prev_value, curr_value):
"""
Using in order traversal, we check if every node is sorted because if it's a BST, then it should be sorted.
"""
if root is not None:
if not isItBST(root.left, prev_value, curr_value):
print "wrong!"
return False
curr_value = root.data
print root.data, "curr: ",curr_value, "prev_value: ",prev_value
if (prev_value > curr_value ):
print "wrong!"
return False
prev_value = curr_value
return isItBST(root.right, prev_value, curr_value)
else:
return True

Categories