Confusion about recursion in a BST python - 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

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

Construct a function which can form a Binary Search Tree recursively from an unsorted list/array of Integers

I've been looking into for quite some time , but can't come up with a proper solution.
Sure we can sort a list of integers to form a BST with the help of a recursive function and call it a day, but there's just too many operations being performed.
I did create a simple function that forms a BST from an unsorted list of integers, but it isn't through recursion.
class:
class Tree:
def __init__(self,key):
self.left = None
self.right = None
self.key = key
function which displays the Tree(rotated 90°)
def display_keys(node, space='\t', level=0):
# print(node.key if node else None, level)
# If the node is empty
if node is None:
print(space*level + '∅')
return
# If the node is a leaf
if node.left is None and node.right is None:
print(space*level + str(node.key))
return
# If the node has children
display_keys(node.right, space, level+1)
print(space*level + str(node.key))
display_keys(node.left,space, level+1)
Source for the display function
function which takes up a single element and adds it to the Tree...
def gen_BST(node,key):
if node is None:
node = Tree(key)
elif key < node.key:
node.left = gen_BST(node.left,key)
else:
node.right = gen_BST(node.right,key)
return node
int_list = [4,2,7,5,1,9,8,6,3]
tree = None
for val in int_list:
tree = gen_BST(tree,val)
Display:
display_keys(tree)
So once again, is there any way to create a recursive function which to form a BST from an unsorted list of integers.
Edit:
The function should take up the entire list as an argument.

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()

Print a binary tree, python, in inorder

Me and my friend are doing some school work with programming in Python 3.1 and are VERY stuck. We're programming a binary tree and it's working fine except when we want to print all the nodes in inorder in a way that would create a sentence (all the words in inorder just after one another in a row). We have been looking all over the internet for clues as to how to procede and we've been working with this little thing for like two hours. Any advice/help would be awesome.
Our program/Binary tree:
class Treenode:
def __init__(self, it = None, le = None, ri = None):
self.item = it
self.left = le
self.right = ri
class Bintree:
def __init__(self):
self.item = None
self.left = None
self.right = None
def put(self, it = None):
key = Treenode(it)
if self.item == None:
self.item = key
return
p = self.item
while True:
if key.item < p.item:
if p.left == None:
p.left = key
return
else:
p = p.left
elif key.item > p.item:
if p.right == None:
p.right = key
return
else:
p = p.right
else:
return
def exists(self, it):
key = it
p = self.item
if p == key:
return True
while True:
if key < p.item:
if p.left == None:
return False
else:
p = p.left
elif key > p.item:
if p.right == None:
return False
else:
p = p.right
else:
return
def isEmpty(self):
if self.item == None:
return True
else:
return False
def printtree (Treenode):
if Treenode.left != None:
printtree (Treenode.left)
print (Treenode.item)
if Treenode.right != None:
printtree (Treenode.right)
We get a sort of print when we run the program which looks like this: "bintree.Treenode object at 0x02774CB0", which is not what we want.
We use the tree by running this:
import bintree
tree = bintree.Bintree()
print(tree.isEmpty()) # should give True
tree.put("solen")
print(tree.isEmpty()) # should give False
tree.put("gott")
tree.put("sin")
tree.put("hela")
tree.put("ban")
tree.put("upp")
tree.put("himlarunden")
tree.put("manen")
tree.put("seglar")
tree.put("som")
tree.put("en")
tree.put("svan")
tree.put("uti")
tree.put("midnattsstuden")
print(tree.exists("visa")) # should give False
print(tree.exists("ban")) # should give True
tree.printtree() # print sorted
Also, the second last row gives us "None" instead of "True", which is wierd.
To print a binary tree, if you are printing a leaf you just print the value; otherwise, you print the left child then the right child.
def print_tree(tree):
if tree:
print tree.value
print_tree(tree.left)
print_tree(tree.right)
print(tree.exists("visa")) returns None, because in the last line of exists() there's return statement without any value (which defaults to None).
Also you shouldn't name a printtree argument Treenode since it's a name of an existing class and that might lead to confusion. It should look more like:
def printtree(tree_node):
if tree_node.left is not None:
printtree(tree_node.left)
print(tree_node.item)
if tree_node.right is not None:
printtree(tree_node.right)
Another thing is calling printtree - it's a function, not Bintree method, so I suppose you should call it printtree(tree).
One way to make testing easier is to use -assert()- instead of printing things and then referring back to your code.
tree = Bintree()
assert(tree.isEmpty())
tree.put("solen")
assert(not tree.isEmpty())
tree.put("gott")
tree.put("sin")
tree.put("hela")
tree.put("ban")
http://docs.python.org/reference/simple_stmts.html#the-assert-statement
It raises an error if its condition is not true. I know that doesn't fix your bug but making things less ambiguous always helps debugging.
You are not specifying a starting case for printtree(). You're defining how to recurse through your tree correctly, but your call to printtree() has no node to start at. Try setting a default check to see if a parameter is passed in, and if one isn't start at the head node of the bintree.
The reason your second to last line is printing None is because, in your exists method, you just have a "return", rather than a "return True", for the case of finding a `p.item' that is equal to key.

Categories