Check for BST using Level Order Traversal - python

So I have been trying out questions from Hackerrank and there has been a question on checking whether a tree is BST or not. I have been using the level order traversal for this check. if node.left.value> node.value and node.right.value<node.value the Tree must not be a BST.
Apart from the condition where I haven't checked for a single root node, all the other test cases fail. Where have I been going wrong?
def checkBST(root):
# if root.left and root.right is None:
# return True
if not root:
return
flag=0
q=[root]
while len(q)>0:
temp=q.pop(0)
if temp.left is not None:
if temp.left.data> temp.data:
flag=1
break
else:
q.append(temp.left)
if temp.right is not None:
if temp.right.data<temp.data:
flag=1
break
else:
q.append(temp.right)
if flag==1:
return False
else:
return True

It is not enough to test that a direct child is in the correct relation to its parent. There are invalid BSTs for which all those comparison-tests pass. You need to ensure that in the whole left subtree of a node there is no node with a greater value than that node's value.
So a level order traversal is not really the right tool to do this. It will work better with an in-order traversal, since a BST will have an non-decreasing in-order traversal.

Related

Binary Tree Search Chech Algorithm Python Not Working

I wrote this algorithm for a coding challenge on HackerRank to determine if a give binary tree is a BST. Yet in some cases when the tree is not a BST my algorithm returns True anyway. I couldn't find what was wrong, everything seems ok, right? Or is there something I don't know about BSTs?
Node is defined as:
class node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
My algorithm is
def checkBST(root):
if not root:
return True
else:
if root.left and root.left.data >= root.data:
return False
if root.right and root.right.data <= root.data:
return False
return checkBST(root.left) and checkBST(root.right)
A binary search tree has all the nodes on the left branch less than the parent node, and all the nodes on the right branch greater than the parent node.
So your code fails on a case like this:
5
/ \
4 7
/ \
2 6
It's not a valid BST because if you searched for 6, you'd follow the right branch from the root, and subsequently fail to find it.
Take a look at a picture below for which your code is giving an incorrect answer:
Where are you going wrong:
1. if an element exists on the left subtree if there exists a node with a value greater than root.
2. if an element exists on the right subtree if there exists a node with a value smaller than root.
You should try this approach :
if not root:
return True
else:
if root.left and maximumOfSubtree(root.left) >= root.data:
return False
if root.right and minimumOfSubtree(root.right) <= root.data:
return False
return checkBST(root.left) and checkBST(root.right)
so the problem is to determine whether the given tree is a BST or not.
the best way to find out is through an inorder traversal.
Do In-Order Traversal of the given tree and store the result in a temp array.
Check if the temp array is sorted in ascending order, if it is, then the tree is BST.
this can be the approach
def check_binary_search_tree_(root):
visited = []
def traverse(node):
if node.left: traverse(node.left)
visited.append(node.data)
if node.right: traverse(node.right)
traverse(root)
fc = {}
for i in visited:
if i in fc:
return False
else:
fc[i]=1
m = sorted(visited)
if visited==m:
return True
return False
refer to this for other methods https://www.geeksforgeeks.org/a-program-to-check-if-a-binary-tree-is-bst-or-not/
method 1 and method 2 are similar to your approach so it will help you to understand that too.

Checking if a treeNode is an ancestor of another node

def is_ancestor(node, middle):
# search down
if node.data == middle.data:
return True
if node.left:
is_ancestor(node.left, middle)
if node.right:
is_ancestor(node.right, middle)
return False
I am using this function to recursively check is a node is an ancestor of middle.
Let's say we have a tree that looks like
5
/
2
\
4
and I say that node is the node that points to 5 and middle is 2.
When calling is_ancestor(node_with_5, node_with_2), I am expecting to recursively move the node down both to left and right and return True whenever it finds the middle.
However, my current function gives me False even though it will find the middle in the first recursion call.
Any help?
You would have to make some little changes à la:
def is_ancestor(node, middle):
if node is middle: # data could coincide, compare nodes directly
return True
if node.left and is_ancestor(node.left, middle):
return True # do actually return something
if node.right and is_ancestor(node.right, middle):
return True # do actually return something
return False
You could get the entire logic in an even more concise way:
def is_ancestor(node, middle):
if node is None:
return False
if node is middle:
return True
return is_ancestor(node.left, middle) or is_ancestor(node.right, middle)

Iterative postorder traversal of a binary tree with a single stack, how to approach the problem?

I have been studying up on algorithms and data structures and I wrote a post-order traversal for a binary tree without using recursion and using only one stack.
Here is the code:
def postorder_iterative(self):
current = self
s = []
current1 = None
done = 0
def peek(s):
return s[-1]
while(not done):
if current and (current != current1):
s.append(current)
current = current.leftChild
elif(len(s) > 0):
if peek(s).rightChild and peek(s).rightChild != current:
current = peek(s).rightChild
else:
current = current1 = s.pop()
print(current.key)
else:
done = 1
This code actually works but it took me forever to come up with it.
Can someone explain what is the intuitive way of thinking about this problem?
I'd like to be able to reproduce it using logic and not spend as much time as I did on it.
Post-order traversal requires that you only print the current node value after traversing both the left and right subtrees. You are using the stack to traverse the left tree only, and use the current1 variable (the last node printed) to know that you are now backing out of a right-hand side tree so you can print the current node.
I'd rename current to node, current1 to last (for last printed), remove the peek() function to just reference stack[-1] directly as tos (top of stack), and simplify your approach to:
def postorder_iterative(self):
node, last = self, None
stack = []
while True:
if node and node is not last:
# build up the stack from the left tree
stack.append(node)
node = node.leftChild
elif stack:
# no more left-hand tree to process, is there a right-hand tree?
tos = stack[-1]
if tos.rightChild and tos.rightChild is not node:
node = tos.rightChild
else:
# both left and right have been printed
node = last = stack.pop()
print(last.key)
else:
break
It is still hard to follow what is going on however, as the connection between last and the point where the left and right subtrees have been processed isn't all that clear.
I'd use a single stack with a state flag to track where in the process you are:
def postorder_iterative(self):
new, left_done, right_done = range(3) # status of node
stack = [[self, new]] # node, status
while stack:
node, status = stack[-1]
if status == right_done:
stack.pop()
print(node.key)
else:
stack[-1][1] += 1 # from new -> left_done and left_done -> right_done
# new -> add left child, left_done -> add right child
next_node = [node.leftChild, node.rightChild][status]
if next_node is not None:
stack.append((next_node, new))
Nodes go through three states, simply by incrementing the state flag. They start as new nodes, then progress to left, then right, and when the top of the stack is in that last state we remove it from the stack and print the node value.
When still in the new or left states, we add the left or right node, if present, to the stack as a new node.
Another approach pushes the right-hand tree onto the stack before the current node. Then later, when you return to the current node, having taken it from the stack, you can detect the case where you still need to process the right-hand side because the top of the stack will have the right-hand node. In that case you swap the top of the stack with the current node and continue from there; you'll later return to the same place and will no longer have that right-hand side node on the top of the stack so you can print:
def postorder_iterative(self):
stack = []
node = self
while node or stack:
while node:
# traverse to the left, but add the right to the stack first
if node.rightChild is not None:
stack.append(node.rightChild)
stack.append(node)
node = node.leftChild
# left-hand tree traversed, time to process right or print
node = stack.pop()
if stack and node.rightChild is stack[-1]:
# right-hand tree present and not yet done, swap tos and node
node, stack[-1] = stack[-1], node
else:
print(node.key)
node = None

Breaking out of the recursion in multi nested tree python

I have a multi nested tree structure and i am trying to find the current level of the provided string/node.I an using recursion to traverse the nodes and return the current level.
def dicq(self,value,current_level):
d = {}
for child in self.children:
if child.name == value:
print current_level
else:
(child.dicq(value,current_level+1))
return current_level
root.dicq('7.3',1)
root is the nested tree structure and i am giving it 7.3(The node which i am finding the level of) and 1(the default level meaning first children). If i simply print the current_level in the if statement it is correct but if i put return in the if statement it doesn't return.I want to return the current level as soon as i find the node.Any suggestions?
Right now your code is returning current_level irrespective of wheather node you are looking for is found or not.
What you need to do is to return current_level only if matching node is found. If not, return something which indicates matching node is not found. Also, you need to ensure your results propagates properly across levels of recursion and you stop looking further when a match is found.
Here is code which might help. Note: I have not tested it.
def dicq(self,value,current_level):
d = {}
retval = -1
for child in self.children:
if child.name == value:
retval = current_level
else:
retval = child.dicq(value,current_level+1)
if retval != -1:
break
return retval
If value is found, that level will be returned. If not, -1 will be returned indicating that its not found anywhere in that part of the tree.

Size of subtree in Python

Almost every online tutorial I see on the subject when it comes to finding the size of a subtree involves calling a recursive function on each child's subtree.
The problem with this in Python is that it overflows if you recurse past a few hundred levels, so if I theoretically had a long, linear tree, it would fail.
Is there a better way to handle this? Do I need to use a stack instead?
Do I need to use a stack instead?
Sure, that's one way of doing it.
def iter_tree(root):
to_explore = [root]
while to_explore:
node = to_explore.pop()
yield node
for child in node.children:
to_explore.append(child)
def size(root):
count = 0
for node in iter_tree(root):
count += 1
return count
The stack would be the easiest non-recursive way of getting the size of the subtree (count of nodes under the given node, including the current node)
class Node():
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def subtree_size(root):
visited = 0
if not root: return visited
stack = [root]
while stack:
node = stack.pop()
visited += 1
if node.left: stack.append(node.left)
if node.right: stack.append(node.right)
return visited
You can mirror the recursive algorithm using a stack:
numNodes = 0
nodeStack = [(root,0)] # (Node,0 means explore left 1 means explore right)
while nodeStack:
nextNode, leftOrRight = nodeStack.pop()
if not nextNode: #nextNode is empty
continue
if leftOrRight == 0:
numNodes += 1
nodeStack.append((nextNode,1))
nodeStack.append((nextNode.leftChild,0))
else:
nodeStack.append((nextNode.rightChild,0))
print(numNodes)
Some things to notice: This is still a Depth-first search! That is, we still fully explore a subtree before starting to explore the other. What this means to you is that the amount of additional memory required is proportional to the height of the tree and not the width of the tree. For a balanced tree the width of the tree is 2^h where h is the height of the tree. For a totally unbalanced tree the height of the tree is the number of nodes in the tree, whereas the width is one! so it all depends on what you need :)
Now It is worth mentioning that you can make a potential optimization by checking if one of the subtrees is empty! We can change the body of if leftOrRight == 0: to:
numNodes += 1
if nextNode.rightChild: #Has a right child to explore
nodeStack.append((nextNode,1))
nodeStack.append((nextNode.leftChild,0))
Which potentially cuts down on memory usage :)

Categories

Resources