I'm working on a binary search tree class and we have to implement the remove method using recursion. Here is the code that was given to us.
def remove (self, x):
def recurse (p):
# Modify this method.
if p==None:
return p
elif x<p.data:
return p
elif x>p.data:
return p
elif p.left==None and p.right==None: # case (1)
return p
elif p.right==None: # case (2)
return p
elif p.left==None: # case (3)
return p
else: # case (4)
return p
self.root = recurse (self.root)
Obviously there should be more than just return p for each conditional. And I'm pretty sure the first if and two elifs are used to 'locate' the node containing x. However I am not sure how to implement the four cases. Any input would be appreciated.
Also, the end goal is to use this method to iterate through the BST and remove each node.
Well, your first step is to locate X, remember that you do this by recursion, so you should recurse the remove function on the child tree its located. (left if x < p.data, right if higher... only if they exist). Next, you need to worry what to do once you find x (x = p.data).
I would recommend drawing a tree and think as an algorithm. :)
Remember the BST property! The tree will necesarily modify it's structure to preserve the property, so.. who is the new father of the lonely childs?
hints:
a sibling possibly?
.
remember the BST charasteristic
.
recursion!!!)
pseudo_code method_recurse requires argument: this_node
if this_node is None then return None Found
if this_node is target then return this_node
if this_node is less_than target then return call self with this_node.right
if this_node is greater_than target then return call self with this_node.left
Related
I am doing Leet Code question 572. Subtree of Another Tree:
Given the roots of two binary trees root and subRoot, return true if there is a subtree of root with the same structure and node values of subRoot and false otherwise.
A subtree of a binary tree tree is a tree that consists of a node in tree and all of this node's descendants. The tree tree could also be considered as a subtree of itself.
I had a working solution. However, when trying a slightly different approach, I ran into a problem with my recursive dfs function. The structure of my code is this:
def isSubTree(self, root, subRoot):
def dfs(root, subRoot):
# some function that returns True when certain conditions are met
if not root: return
self.isSubtree(root.left, subRoot)
self.isSubtree(root.right, subRoot)
if root.val == subRoot.val:
if dfs(root, subRoot):
return True
return False
Basically, isSubTree explores all the nodes in the tree. Once it finds a node with the same value as subRoot, it compares the sub tree rooted at that node with the sub tree rooted at subRoot with dfs().
I intended that when the dfs() function returns true, isSubTree() will also return true. If I've explored all the nodes in the tree (isSubTree() reaches the end) and dfs() hasn't returned true
at all, isSubTree() will return False.
However,my code always returns false seemingly because of the last line where it returns False (I've tested it and can verify that the return True part in if dfs() was reached, also I'm pretty sure my dfs() function is correct).
My question is, is there an elegant way to have my code do what I want it to do?
It is hard to see which code you are using, since obviously there is a syntax problem just below def dfs. Either the indentation is off or there is code missing (at the time of writing).
There are these issues:
The returned value from either recursive self.isSubTree calls is not used at all. This cannot be right, as certainly you need that information to conclude anything about the work done.
if not root: return will not return a boolean value when it kicks in. This should make the distinction between the case where also subTree is None: in that case the returned value should be True. Otherwise it should be False
Assuming your non-provided dfs code is correct, it will deal well with the previous point as well (where one or both of the arguments is None), and will compare the node values correctly, so then if not root should not be performed before dfs is called, as dfs will take care of that.
Similarly if root.val == subTree.val should not have to occur where it now occurs, but only in dfs.
Concluding, the working code could be like this:
class Solution(object):
def isSubtree(self, root, subRoot):
def dfs(root, subRoot):
if not root or not subRoot or root.val != subRoot.val:
return root == subRoot # Must both be None to have success
return dfs(root.left, subRoot.left) and dfs(root.right, subRoot.right)
return dfs(root, subRoot) or root and (
self.isSubtree(root.left, subRoot) or self.isSubtree(root.right, subRoot)
)
Solved it with the following:
def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
def dfs(r1, r2):
# some function
if not root:
return False
if root.val == subRoot.val:
if dfs(root, subRoot):
return True
return self.isSubtree(root.left, subRoot) or self.isSubtree(root.right, subRoot)
I am trying to understand below recursion function which says whether a particular node exists in a binary tree. I did my homework and got most of the recursion part but the last return statement (return root.value == value or inleft or inright) bothers me.
can someone please help me in understanding this method?
def existsInTree(self, root, value):
if root is None:
return False
else:
inleft = self.existsInTree(root.left, value)
inright = self.existsInTree(root.right, value)
print(inleft,inright)
return root.value == value or inleft or inright
example binary tree:
10
/ \
11 9
we will first compare data of root with data of node to be searched. If the match is found, set the flag to true. Else, search the node in left subtree and then in the right subtree.
There's another way of looking at that return statement, you can split the return statement at the or keyword
def ifRootExists(self,root, value):
if (root == None):
return False
if (root.value == value):
return True
""" then recur on left sutree """
res1 = ifrootExists(root.left, value)
# root found, no need to look further
if res1:
return True
""" root is not found in left,
so recur on right subtree """
res2 = ifrootExists(root.right, value)
return res2
We can get the result from the above function whether some node exists.
The algorithm is as follows.
root is None or Not. If None, get back the position which the parent function called with the value of "False".
Otherwise, the function continuously search based on the current node.
inleft is a value of function "existsInTree" in which the current node's left child is the root node.
inright is a value of function "existsInTree" in which the current node's right child is the root node.
Let's assume that we want to search value as called V.
Which V exists in the tree means the current value is V or in the left tree, or in the right tree.
To summarize, inleft and inright means whether V includes or not in the subtree.
I'm trying to build a BST (binary search tree) with dict in python. I do not understand why my code is not adding nodes to the BST. I saw a similar post here:
How to implement a binary search tree in Python?
which looks the same as my code except declaring a node class, but I would like to know why my dict implementation fails (and hopefully improve my understanding of parameter passing with recursion in python).
keys = [10,9,2,5,3,7,101,18]
start = {'key': keys[-1], 'val': 1, 'left': None, 'right': None}
def binarySearch(root, node):
# compare keys and insert node into right place
if not root:
root = node
elif node['key'] < root['key']:
binarySearch(root['left'], node)
else:
binarySearch(root['right'], node)
# Now let's test our function and build a BST
while keys:
key = keys.pop()
node = {'key': key, 'val': 1, 'left': None, 'right': None}
binarySearch(start, node)
print(start) # unchanged, hence my confusion. Thx for your time!
===========================================
Edit: here is the code that would make it work!
def binarySearch(root, node):
# compare keys and insert node into right place
if not root:
root = node
elif node['key'] < root['key']:
if not root['left']: root['left'] = node
else: binarySearch(root['left'], node)
else:
if not root['right']: root['right'] = node
else: binarySearch(root['right'], node)
Here is what I think that is happening under the hood (why one version is able to add to BST but the other one is not):
In the original version, we will reach a recursion call where root still points to None inside the BST, but then root = node make root points to node which has absolutely no connection with start, i.e. the BST itself. Then local variables are deleted and no changes are made.
In the modified version, we will avoid this since when we add the node by e.g. root['left'] = node. Here root is still pointing to the original BST and thus we are modifying the key-val pair in the original BST instead of having root point to something totally outside the BST.
Let's run through your code as though we were the python interpreter.
Lets start at the first call: binarySearch(start, node)
Here start is the dict defined at the top of your script and node is another dict (which curiously has the same value).
Lets jump inside the call and we find ourselves at: if not root: where root refers to start above and so is truthy so fails this if.
Next we find ourselves at: elif node['key'] < root['key']: which in this case is not True.
Next we pass into the else: and we are at: binarySearch(root['right'], node).
Just before we jump into the first recursive call, lets review what the parameters to the call are: root['right'] from start has the value None and node is still the same dict which we want to insert somewhere. So, onto the recursive call.
Again we find ourselves at: if not root:
However this time root just refers to the first parameter of the first recursive call and we can see from the above review of the parameters that root refers to None.
Now None is considered falsy and so this time the if succeeds and we are on to the next line.
Now we are at root = node.
This is an assignment in python. What this means is that python will use the variable root to stop referring to None and to refer to whatever node currently refers to, which is the dict which was created in the while loop. So root (which is just a parameter, but you can think of as a local variable now) refers to a dict.
Now what happens is that we are at the end of the first recursive call and this function ends. Whenever a function ends, all the local variables are destroyed. That is root and node are destroyed. That is just these variables and not what they refer to.
Now we return to just after the first call site i.e. just after binarySearch(root['right'], node)
We can see here that the parameters: root['right'], node still refer to whatever they were referring to before. This is why your start is unchanged and why your program should deal with left and right now instead of recursing.
#Creted by The Misunderstood Genius
def add_root(e,key):
''''
e is node's name
key is the node's key search
'''
bst=dict()
bst[e]={'key':key,'P':None,'L':None,'R':None}
return bst
def root(tree):
for k,v in tree.items():
if v['P'] == None:
return k
def insert(tree, node, key):
tree[node]={'key':key,'P':None,'L':None,'R':None}
y =None
x = root(tree)
node_key = tree[node]['key']
while x is not None:
y=x
node_root=tree['R']['key']
if node_key < node_root:
x=tree[x]['L']
else:
x=tree[x]['R']
tree[node]['P']=y
if y is not None and node_key< tree[y]['key']:
tree[y]['L']=node
else:
tree[y]['R']=node
return tree
def print_all(tree):
for k,v in tree.items():
print(k,v)
print()
'''
Give a root node and key search target
Returns the name of the node with associated key
Else None
'''
def tree_search(tree,root, target):
if root ==None:
print(" key with node associate not found")
return root
if tree[root]['key'] == target:
return root
if target < tree[root]['key']:
return tree_search(tree,tree[root]['L'],target)
else:
return tree_search(tree,tree[root]['R'],target)
def tree_iterative_search(tree,root,target):
while root is not None and tree[root]['key']!=target:
if target < tree[root]['key']:
root=tree[root]['L']
else:
root=tree[root]['R']
return root
def minimum(tree,root):
while tree[root]['L'] is not None:
root=tree[root]['L']
return tree[root]['key']
bst=add_root('R',20)
bst=insert(bst,'M',10)
bst=insert(bst,'B',8)
bst=insert(bst,'C',24)
bst=insert(bst,'D',22)
bst=insert(bst,'E',25)
bst=insert(bst,'G',25)
print_all(bst)
print(tree_search(bst,'R',25))
x=tree_iterative_search(bst,'R',25)
print(x)
#print(minimum(bst,'R'))
I've been playing with BST (binary search tree) and I'm wondering how to do an early exit. Following is the code I've written to find kth smallest. It recursively calls the child node's find_smallest_at_k, stack is just a list passed into the function to add all the elements in inorder. Currently this solution walks all the nodes inorder and then I have to select the kth item from "stack" outside this function.
def find_smallest_at_k(self, k, stack, i):
if self is None:
return i
if (self.left is not None):
i = self.left.find_smallest_at_k(k, stack, i)
print(stack, i)
stack.insert(i, self.data)
i += 1
if i == k:
print(stack[k - 1])
print "Returning"
if (self.right is not None):
i = self.right.find_smallest_at_k(k, stack, i)
return i
It's called like this,
our_stack = []
self.root.find_smallest_at_k(k, our_stack, 0)
return our_stack[k-1]
I'm not sure if it's possible to exit early from that function. If my k is say 1, I don't really have to walk all the nodes then find the first element. It also doesn't feel right to pass list from outside function - feels like passing pointers to a function in C. Could anyone suggest better alternatives than what I've done so far?
Passing list as arguments: Passing the list as argument can be good practice, if you make your function tail-recursive. Otherwise it's pointless. With BST where there are two potential recursive function calls to be done, it's a bit of a tall ask.
Else you can just return the list. I don't see the necessity of variable i. Anyway if you absolutely need to return multiples values, you can always use tuples like this return i, stack and this i, stack = root.find_smallest_at_k(k).
Fast-forwarding: For the fast-forwarding, note the right nodes of a BST parent node are always bigger than the parent. Thus if you descend the tree always on the right children, you'll end up with a growing sequence of values. Thus the first k values of that sequence are necessarily the smallest, so it's pointless to go right k times or more in a sequence.
Even in the middle of you descend you go left at times, it's pointless to go more than k times on the right. The BST properties ensures that if you go right, ALL subsequent numbers below in the hierarchy will be greater than the parent. Thus going right k times or more is useless.
Code: Here is a pseudo-python code quickly made. It's not tested.
def findKSmallest( self, k, rightSteps=0 ):
if rightSteps >= k: #We went right more than k times
return []
leftSmallest = self.left.findKSmallest( k, rightSteps ) if self.left != None else []
rightSmallest = self.right.findKSmallest( k, rightSteps + 1 ) if self.right != None else []
mySmallest = sorted( leftSmallest + [self.data] + rightSmallest )
return mySmallest[:k]
EDIT The other version, following my comment.
def findKSmallest( self, k ):
if k == 0:
return []
leftSmallest = self.left.findKSmallest( k ) if self.left != None else []
rightSmallest = self.right.findKSmallest( k - 1 ) if self.right != None else []
mySmallest = sorted( leftSmallest + [self.data] + rightSmallest )
return mySmallest[:k]
Note that if k==1, this is indeed the search of the smallest element. Any move to the right, will immediately returns [], which contributes to nothing.
As said Lærne, you have to care about turning your function into a tail-recursive one; then you may be interested by using a continuation-passing style. Thus your function could be able to call either itself or the "escape" function. I wrote a module called tco for optimizing tail-calls; see https://github.com/baruchel/tco
Hope it can help.
Here is another approach: it doesn't exit recursion early, instead it prevents additional function calls if not needed, which is essentially what you're trying to achieve.
class Node:
def __init__(self, v):
self.v = v
self.left = None
self.right = None
def find_smallest_at_k(root, k):
res = [None]
count = [k]
def helper(root):
if root is None:
return
helper(root.left)
count[0] -= 1
if count[0] == 0:
print("found it!")
res[0] = root
return
if count[0] > 0:
print("visiting right")
find(root.right)
helper(root)
return res[0].v
If you want to exit as soon as earlier possible, then use exit(0).
This will make your task easy!
I am having trouble finding a node in a tree with arbitrary branching factor. Each Node carries data and has zero or greater children. The search method is inside the Node class and
checks to see if that Node carries data and then checks all of that Nodes children. I keep ending up with infinite loops in my recursive method, any help?
def find(self, x):
_level = [self]
_nextlevel = []
if _level == []:
return None
else:
for node in _level:
if node.data is x:
return node
_nextlevel += node.children
_level = _nextlevel
return self.find(x) + _level
The find method is in the Node class and checks if data x is in the node the method is called from, then checks all of that nodes children. I keep getting an infinite loop, really stuck at this point any insight would be appreciated.
There are a few issues with this code. First, note that on line 2 you have _level = [self]. that means the if _level == [] on line 5 will always be false.
The 2nd issue is that your for loop goes over everything in _level, but, as noted above, that will always be [self] due to line 2.
The 3rd issue is the return statement. You have return self.find(x) + _level. That gets evaluated in 2 parts. First, call self.find(x), then concatenate what that returns with the contents of _level. But, when you call self.find(x) that will call the same method with the same arguments and that, in turn, will then hit the same return self.find(x) + _level line, which will call the same method again, and on and on forever.
A simple pattern for recursive searches is to use a generator. That makes it easy to pass up the answers to calling code without managing the state of the recursion yourself.
class Example(object):
def __init__(self, datum, *children):
self.Children = list(children) # < assumed to be of the same or duck-similar class
self.Datum = datum
def GetChildren(self):
for item in self.Children:
for subitem in item.GetChildren():
yield subitem
yield item
def FindInChildren(self, query): # where query is an expression that is true for desired data
for item in self.GetChildren():
if query(item):
yield item