Simple Tree Traversal in Python: Help me debug - python

This is how I would traverse a binary tree in Python
def binary_tree(root):
if root.left:
binary_tree(root.left)
print root
if root.right:
binary_tree(root.right)
if I need to return the traversed path:
def binary_tree(node, path):
if root.left:
binary_tree(root.left)
path.append(root)
if root.right:
binary_tree(root.right)
return path
Okay, easy enough. I am confident in tree traversals, so I try the following.
def nary_tree(root, value):
"""return True if there is a node with value exists in root"""
if not root: #empty tree
return False
if root.left:
nary_tree(root.left, value)
if root.data == value: #recurse up until the current node has a right child
return True
if root.right:
nary_tree(root.right, value)
return False
This does not return True when it should. So I try to debug, stepping into the function. I realize that I am not supposed to escape a recursion just by returning a value. The above code will return True once and False many times, in case there is a matching node, and I will almost always get a False. So I try the following:
def nary_tree(root, value):
"""return True if there is a node with value exists in root"""
if not root: #empty tree
return False
if root.left:
return nary_tree(root.left, value)
if root.data == value:
#but in this case, this is never executed
return True
if root.right:
return nary_tree(root.right, value)
return False #how was this being executed in above example then?
Questions:
What am I misunderstanding?
How would you fix the above code?
I have been fairly comfortable at writing recursive functions, but I seem to be still confused.

Even if the current node has the data, if it has a left node, you are returning from the function. Ideally, this should have been like this
def nary_tree(root, value):
"""return True if there is a node with value exists in root"""
if not root: #empty tree
return False
if root.data == value:
return True
if nary_tree(root.left, value):
return True
if nary_tree(root.right, value):
return True
return False #how was this being executed in above example then?

Related

Removing Root Node in Binary Search Tree

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

Calling a recursive function inside a class

I am trying to call a recursive method in order to find path from root to node in a binary tree. There are few solns. for this problem on the internet, but I am trying to use slightly different approach by implementing a method inside a Node class.
Here is my logic for the soln.
def apend(self, arr, target):
""" arr is the list which has the path from root to target node, self is the root """
if self is None:
return False
arr.append(self.data)
if self.data==target:
return True
if self.left.apend(arr, target) or self.right.apend(arr, target):
return True
arr.pop()
return False
I a perfectly okay with how this logic is working, means if the target is found in either right or left subtree return True.
My question is; what if self is a leaf node, i.e. self.left is None. same with self. right. In that case the recursive call is giving an error.
Can I get some help on how to rectify that situation? thanx
Instead of checking if self is None you need to check if self.left or self.right is None before you make a recursive call.
def apend(self, arr, target):
""" arr is the list which has the path from root to target node, self is the root """
arr.append(self.data)
if self.data==target:
return True
if self.left is not None and self.left.apend(arr, target):
return True
if self.right is not None and self.right.apend(arr, target):
return True
arr.pop()
return False

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

Error on checking if a tree is a Binary Search Tree

I am currently trying to check if a tree is a BST, while keeping notice of the fact that the values must not be equal to any other one in the tree. I tried keeping count of the interval on which each value should be ( considering a min and a max as arg[0] and arg[1]).
If we are for example going all the way down on the left subtree, there will be no min, only a max. However, when we switch to the right, we will also have a minimum ( the value of the root node we just switched right from).
However, my code is not showing the right answer and i have no idea why. Could you please help me?
These are my functions: ( i am resolving this on hackerrank therefore that's why i have two functions instead of one)
""" Node is defined as
class node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
"""
def check_binary_search_tree_(root):
check_bst(root,None,None)
def check_bst(root,*arg):
res, res2 = True, True
if arg[0] is None and arg[1] is not None:
if root.data >=arg[1]:
return False
elif arg[1] is None and arg[0] is not None:
if root.data <= arg[0]:
return False
elif arg[1] is not None and arg[0] is not None and (root.data<=arg[0] or root.data >= arg[1]):
return False
if root.left:
res = check_bst(root.left, arg[0], root.data)
if root.right:
res2= check_bst(root.right, root.data, arg[1])
if not res or not res2:
return False
return True
Your problem here is that you don't have the check_binary_search_tree_ function that HackerRank calls returning anything. Instead of this
def check_binary_search_tree_(root):
check_bst(root,None,None)
you should be doing this
def check_binary_search_tree_(root):
return check_bst(root,None,None)

Function to determine whether tree is a valid BST?

I have to determine whether given a list representing a tree, whether the tree is a valid BST (this question is taken from leetcode). I have seen other posts on this but I was wondering if someone could help me with my approach, since it is clearly not right. For example, for the tree [1,2,3] where 1 is the root, 2 is the left child, and 3 is the right child, my code returns true. Hopefully it only requires small changes, but it might be that the entire function's approach is incorrect.
Here is my code:
def isValidBST(self, root):
if (root == None):
return True
if (root.left == None or root.left.val < root.val):
return self.isValidBST(root.left)
if (root.right == None or root.right.val > root.val):
return self.isValidBST(root.right)
return False
Secondly, I have seen approaches with a helper function that takes in a min/max value, but that confuses me. If anyone would also like to explain why that approach is a good/better one, that would be greatly appreciated!
I'd make a min_max method for Nodes that finds the min and max values of the tree rooted at that Node. Do sanity checking while finding those, and then isValidBST can just catch the exception
def max_min(self):
'''
Returns maximum and minimum values of the keys of the tree rooted at self.
Throws an exception if the results are not correct for a BST
'''
l_max, l_min = self.left.max_min() if self.left else (self.val, self.val)
if l_max > self.val:
raise ValueError('Not a BST')
r_max, r_min = self.right.max_min() if self.right else (self.val, self.val)
if r_min < self.val:
raise ValueError('Not a BST')
return l_min, r_max
def isValidBST(self):
try:
if self.max_min():
return True
except ValueError:
return False
Here is one way to implement the validity check:
class BST:
def __init__(self, value, left=None, right=None):
self.value = value
self.left = left
self.right = right
def isValidBST(self):
'''
Simultaneously check for validity and set our own min and max values.
'''
self.min = self.max = self.value
if self.left:
if not self.left.isValidBST():
return False
if self.left.max >= self.value:
return False
self.min = self.left.min
if self.right:
if not self.right.isValidBST():
return False
if self.right.min < self.value:
return False
self.max = self.right.max
return True
assert BST(2, BST(1), BST(3)).isValidBST()
case = BST(2, BST(1, None, BST(3)))
assert case.left.isValidBST()
assert not case.isValidBST()

Categories