adding a node in Binary Search Tree - python

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)

Related

question on serialization and deserialization in binary tree

There is this problem asked by Google to design an algorithim to serialize and deserialize binary tree. I found one of the solutions online. The part i don't really understand is why the condition is necessary at line 20, where "if node == None:", self.root = Node(value) ? Because afterall this program will prompt the user to input nodes in the form eg: 1,3,5 in order for the program to work so therefore there won't be a case where node =none because user input is necessary? Am I misunderstanding something else here?
class Node:
def __init__(self, value):
self.left = None
self.right = None
self.value = value
class Tree:
def __init__(self):
self.root = None
def addNode(self, node, value): #Why when node==None, self.root= Node(value)?
if node == None:
self.root = Node(value)
else:
if value < node.value:
if not node.left:
node.left = Node(value)
else:
self.addNode(node.left, value)
else:
if not node.right:
node.right = Node(value)
else:
self.addNode(node.right, value)
def serialize(root):
values = []
def serializer(node):
if not node:
values.append('?')
else:
values.append(str(node.value))
serializer(node.left)
serializer(node.right)
serializer(root)
return ','.join(values)
def deserialize(s):
values = iter(s.split(','))
def deserializer():
val = next(values)
if val == '?':
return None
else:
node = Node(int(val))
node.left = deserializer()
node.right = deserializer()
return node
return deserializer()
if __name__ == '__main__':
# Read input, numbers separated by commas
numbers = [int(n) for n in input().split(',')]
theTree = Tree()
for number in numbers:
theTree.addNode(theTree.root, number)
s1 = serialize(theTree.root)
s2 = serialize(deserialize(s1))
print(s1)
print(s2)
assert s1 == s2
In this line, when first number is entered in the tree, root will be None
for number in numbers:
theTree.addNode(theTree.root, number)
Hence, line 20 is needed.

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

Remove recursively from a BST

I am finished with the case when the node that i want to remove is the root node or the leaf node, but i need to be able to remove also when it has siblings or children, which i am finding very hard.
class Node:
def __init__(self, key=None, data=None):
self.key = key
self.data = data
self.left = None
self.right = None
class BST:
def __init__(self):
self.root = None
self.size = 0
def remove(self, key):
self.root = self._remove(key, self.root)
def insert(self, key, data):
self.root = self._insert(self.root, key, data)
def _insert(self, root, key, data):
if root == None:
self.size += 1
return Node(key, data)
if root.key == key:
return root
elif root.key > key:
root.left = self._insert(root.left, key, data)
else:
root.right = self._insert(root.right, key, data)
return root
def _remove(self, key, node):
if node == None:
return None
if key == node.key:
if node.left != None and node.right == None: # if trying to remove root and right side is empty
return node.left
elif node.left == None and node.right != None: # if trying to remove root and left side is empty
return node.right
elif node.left == None and node.right == None: # if trying to remove leaf
return node
# two more cases to check when it has siblings
# iterates recursively in the bst
elif key <= node.key:
node.left = self._remove(key, node.left)
else:
node.right = self._remove(key, node.right)
return node
I posted the whole code so if anyone wants to test in their machine is welcome to do so, or someone can use it for educational purpose.
In the future try to perform some debugging and provide some sample output and expected output.
Consider below
def _remove(self, key, node):
if node == None:
return None
if key == node.key:
if node.left and not node.right: # only left
return node.left
elif node.right and not node.left: # only right
return node.right
elif not node.right and not node.left: # neither
return None
else : # both
inorder_successor = node.right
while inorder_successor.left:
inorder_successor = inorder_successor.left
# remember to replace inorder_successor with it's right child
...
...
return inorder_successor
# iterates recursively in the bst
elif key <= node.key:
node.left = self._remove(key, node.left)
else:
node.right = self._remove(key, node.right)
return node
A few observations about what changed
You check for None using, is != None which is a very non Pythonic way to it. Just check for is and is not instead
The right way to replace a node in a BST that has both children is with the inorder successor (left most descendant of the right child of the deleted node)

Python Binary Search Tree Delete Function

class Node:
def __init__(self, val):
self.val = val
self.left = None
self.right = None
class BST:
def __init__(self, root=None):
self.root = root
def remove(self, val):
if self.root == None:
return root
else:
self._remove(val, self.root)
def _remove(self, val, node):
if node == None:
return node # Item not found
if val < node.val:
self._remove(val, node.left)
elif val > node.val:
self._remove(val, node.right)
else:
# FOUND NODE TO REMOVE
if node.left != None and node.right != None: # IF TWO CHILDREN
node.val = self._find_min(node.right)
node.right = self._remove(node.val, node.right)
else: # ZERO OR ONE CHILD
if node.left == None: # COVERS ZERO CHILD CASE
node = node.right
elif node.right == None:
node = node.left
return node
Cannot figure out why this function will not delete some values. I debugged with print statements and can see that if I try to remove a value, the function will enter the else block and successfully remove a node with two children. However, when attempting to remove a node with one or zero children, the codes executes with no errors, but when I print the tree to view its contents the node is still there.
The node to be removed will have at least one None child, and it seems straightforward to set the node equal to its right (or left) child, which I assume sets the node to None.
I have some experience with Java, but fairly new to Python, and I sometimes run into trouble with the "self" protocol, but I don't think that is the case here.

Cofusing about lookup node with binary tree

I build a binary tree with python code, now I could print it in order with testTree.printInorder(testTree.root). I have tried to lookup some node ,and the function findNode doesn't work anymore . print testTree.findNode(testTree.root,20) whatever I put in just return None.
class TreeNode:
def __init__(self, value):
self.left = None;
self.right = None;
self.data = value;
class Tree:
def __init__(self):
self.root = None
def addNode(self,node,value):
if node == None:
self.root = TreeNode(value)
else:
if value < node.data:
if node.left == None:
node.left = TreeNode(value)
else:
self.addNode(node.left,value)
else:
if node.right == None:
node.right = TreeNode(value)
else:
self.addNode(node.right,value)
def printInorder(self,node):
if node != None:
self.printInorder(node.left)
print node.data
self.printInorder(node.right)
def findNode(self,node,value):
if self.root != None:
if value == node.data:
return node.data
elif value < node.data and node.left != None:
self.findNode(node.left,value)
elif value > node.data and node.right != None:
self.findNode(node.right,value)
else:
return None
testTree = Tree()
testTree.addNode(testTree.root, 200)
testTree.addNode(testTree.root, 300)
testTree.addNode(testTree.root, 100)
testTree.addNode(testTree.root, 30)
testTree.addNode(testTree.root, 20)
#testTree.printInorder(testTree.root)
print testTree.findNode(testTree.root,20)
Any function without an explicit return will return None.
You have not returned the recursive calls within findNode. So, here.
if value == node.data:
return node.data
elif value < node.data and node.left != None:
return self.findNode(node.left,value)
elif value > node.data and node.right != None:
return self.findNode(node.right,value)
Now, I can't help but thinking this is a bit noisy. You'll always start adding from the root, yes?
testTree.addNode(testTree.root, 200)
You could rather do this
testTree.addNode(200)
And to do that, you basically implement your methods on the TreeNode class instead. So, for the addNode.
You could also "return up" from the recursion, rather than "pass down" the nodes as parameters.
class TreeNode:
def __init__(self, value):
self.left = None
self.right = None
self.data = value
def addNode(self,value):
if self.data == None: # Ideally, should never end-up here
self.data = value
else:
if value < self.data:
if self.left == None:
self.left = TreeNode(value)
else:
self.left = self.left.addNode(value)
else:
if self.right == None:
self.right = TreeNode(value)
else:
self.right = self.right.addNode(value)
return self # Return back up the recursion
Then, in the Tree class, just delegate the addNode responsibility to the root
class Tree:
def __init__(self):
self.root = None
def addNode(self,value):
if self.root == None:
self.root = TreeNode(value)
else:
self.root = self.root.addNode(value)
When you recurse to children in findNode you need to return the result, otherwise the function will implicitly return None:
def findNode(self,node,value):
if self.root != None:
if value == node.data:
return node.data
elif value < node.data and node.left != None:
return self.findNode(node.left,value) # Added return
elif value > node.data and node.right != None:
return self.findNode(node.right,value) # Added return
else:
return None

Categories