Deleting a node with two children in BST - python-3.x

I have implemented a binary search tree and it is working fine except for the case where I have to delete a node with two children. The following is my code for the delete method:
def find(self, node):
curr = node
while curr.left is not None:
curr = curr.left
return curr
def delete(self, key):
node_to_remove = self._search(key, self.root)
if node_to_remove.left is None and node_to_remove.right is None:
#Then we identify this as a leaf node
if node_to_remove is node_to_remove.parent.left:
#Setting the parent's reference to this to None
node_to_remove.parent.left = None
elif node_to_remove is node_to_remove.parent.right:
node_to_remove.parent.right = None
#2nd Case --> Two child
elif node_to_remove.left and node_to_remove.right:
minimum = self.find(node_to_remove.right)
node_to_remove = minimum
self.delete(minimum.key)
#3rd Case -> One child
else:
if node_to_remove.left:
node_to_remove.left.parent = node_to_remove.parent
node_to_remove.parent.left = node_to_remove.left
elif node_to_remove.right:
node_to_remove.right.parent = node_to_remove.parent
node_to_remove.parent.right = node_to_remove.right
Any indicators on how to fix the second case would be of immense help!

You'll need to take the value of the deeper deleted node and assign it to the node that has the two children. So that block should look like this:
elif node_to_remove.left and node_to_remove.right:
minimum = self.find(node_to_remove.right)
self.delete(minimum.key)
node_to_remove.key = minimum.key
Other remarks
Your code will run into an exception when the root has the value to delete and it has no two children. In that case your code wants to access the parent node, which the root node does not have. Furthermore, such a deletion should result in a different value for this.root.
Here is the suggested correction:
def delete(self, key):
node_to_remove = self._search(key, self.root)
if node_to_remove.left is None and node_to_remove.right is None:
if node_to_remove is self.root:
self.root = None
elif node_to_remove is node_to_remove.parent.left:
node_to_remove.parent.left = None
elif node_to_remove is node_to_remove.parent.right:
node_to_remove.parent.right = None
#2nd Case --> Two child
elif node_to_remove.left and node_to_remove.right:
minimum = self.find(node_to_remove.right)
self.delete(minimum.key)
node_to_remove.key = minimum.key
#3rd Case -> One child
else:
if node_to_remove.left:
node_to_remove.left.parent = node_to_remove.parent
if node_to_remove is self.root:
self.root = node_to_remove.left
else:
node_to_remove.parent.left = node_to_remove.left
elif node_to_remove.right:
node_to_remove.right.parent = node_to_remove.parent
if node_to_remove is self.root:
self.root = node_to_remove.right
else:
node_to_remove.parent.right = node_to_remove.right
A second remark: your tree is threaded (i.e. it has parent references). It is possible to do this for a non-threaded tree, and also with less code.

Related

Python pre-order to postfix notation reverse polish notation

I am trying to find a solution in Python to try and move the pre-order notation e.g."* + 7 3 - 2 9" or "+ 55 26" to post notation or the reverse polish notation. the expected result would be e.g. "7 3 + 2 9 - *" and "55 26 +" respectively. I did some research on the binary trees however with the math functions I am struggling. What I have currently is the following:
class Node:
def __init__(self, data):
self.left = None
self.right = None
self.data = data
Function to check for operators
def isOperator(self, c):
return c == '+' or c == '-' or c == '*' or c == '/'
Insert Node
def insert(self, data):
if self.data:
if data < self.data:
if self.left is None:
self.left = Node(data)
else:
self.left.insert(data)
elif data > self.data:
if self.right is None:
self.right = Node(data)
else:
self.right.insert(data)
else:
self.data = data
Print the Tree
def PrintTree(self):
if self.left:
self.left.PrintTree()
print( self.data),
if self.right:
self.right.PrintTree()
Preorder traversal
Root -> Left ->Right
def PreorderTraversal(self, root):
res = []
if root:
res.append(root.data)
res = res + self.PreorderTraversal(root.left)
res = res + self.PreorderTraversal(root.right)
return res
Postorder traversal
Left ->Right -> Root
def PostorderTraversal(self, root):
res = []
if root:
res = self.PostorderTraversal(root.left)
res = res + self.PostorderTraversal(root.right)
res.append(root.data)
return res
Any assistance in getting this code complete will be highly appreciated, as I am not able to figure this out.
Thank you in advance
First some issues:
Your insert method applies binary search tree logic, which is not relevant for the expression tree you want to build. Instead it should keep track of the insertion spot for any next data.
It is not clear whether these are all methods of the Node class, but at least isOperator is unrelated to any instance values, so it should not be a method (with self argument), but either a stand alone function or a static method. Moreover, if the input is valid, then an operator is anything that is not a number.
Methods should not print (except for debugging purposes): leave that for the main driver code to do. Methods can help printing by returning an iterator, or a representation (implementing __repr__).
It is common practice to not use an initial capital letter for function names and reserve that for class names.
I would suggest a separate class for the tree. This will be handy to track where the next node should be inserted, using a path (stack).
Here is how you could implement it:
class Node:
def __init__(self, data, left=None, right=None):
self.left = left
self.right = right
self.data = data
def __iter__(self): # default iteration is inorder
if self.left:
yield from self.left
yield self.data
if self.right:
yield from self.right
def preorder(self):
yield self.data
if self.left:
yield from self.left.preorder()
if self.right:
yield from self.right.preorder()
def postorder(self):
if self.left:
yield from self.left.postorder()
if self.right:
yield from self.right.postorder()
yield self.data
class Tree:
def __init__(self):
self.root = None
self.path = []
def insert(self, data):
if not self.root:
node = self.root = Node(data)
elif not self.path:
raise ValueError("Cannot add more nodes")
elif self.path[-1].left:
node = self.path[-1].right = Node(data)
else:
node = self.path[-1].left = Node(data)
if not data.isnumeric(): # internal node
self.path.append(node)
else:
while self.path and self.path[-1].right:
self.path.pop()
#staticmethod
def fromstr(s):
tree = Tree()
for token in s.split():
tree.insert(token)
return tree
def __iter__(self):
if self.root:
yield from self.root
def preorder(self):
if self.root:
yield from self.root.preorder()
def postorder(self):
if self.root:
yield from self.root.postorder()
Here is how you can run it:
def postorder(s):
tree = Tree.fromstr(s)
# Just for debugging, print the tree in inorder:
print(*tree) # this calls `__iter__`
return " ".join(tree.postorder())
print(postorder("* + 7 3 - 2 9"))
Output (inorder and postorder):
7 + 3 * 2 - 9
7 3 + 2 9 - *

search a node in a binary tree

i have this code that suppose seek the node in a binary tree:
def seekNode(self, node):
if self.id == node:
return self
elif self.left != None:
self.left.seekNode(node)
elif self.right != None:
self.left.seekNode(node)
else:
return False
but it returns a None object.
Here is the complete class:
class BinTree:
def __init__(self, id=None):
self.id= id
self.left= None
self.right= None
def setId(self, id):
if self.id == None:
self.id= id
return self
else:
return self.seekNode(id)
def addChildLeft(self, left):
if self.left == None:
self.left= BinTree(left)
else:
self.left.addChildLeft(left)
def addChildRight(self, right):
if self.right == None:
self.right= BinTree(right)
else:
self.right.addChildRight(right)
def seekNode(self, node):
if self.id == node:
return self
elif self.left != None:
self.left.seekNode(node)
elif self.right != None:
self.left.seekNode(node)
else:
return False
i only call setuId function, i debugged the code (i'm not expert doing this) and i saw that the function returned de object, but when i save this search, it turns in None object.
You are missing return before the recursive calls to self.left.seek(...) and self.right.seek(...). The user of the tree also should not have to concern him/herself with addLeft or addRight - a simple insert method should automatically insert a new node in the proper location.
I would also point out recursion is a functional heritage and using it with functional style will yield the best results. This means avoiding things variable reassignment, other mutations, and side effects.
Let's see what it would look like to implement btree in this way -
# btree.py
def empty():
return None
def insert(t, v):
if not t:
return node(v) # empty
elif v < t.data:
return node(t.data, insert(t.left, v), t.right) # insert left
elif v > t.data:
return node(t.data, t.left, insert(t.right, v)) # insert right
else:
return t # no change
def seek(t, v):
if not t:
return None # no match
elif v < t.data:
return seek(t.left, v) # seek left
elif v > t.data:
return seek(t.right, v) # seek right
else:
return t # match
The data structure for node can be anything you want. Here's a dead-simple interface. Notice how the left and right properties can be set when a new node is constructed -
class node:
def __init__(self, data, left=None, right=None):
self.data = data
self.left = left
self.right = right

delete node in BST python

class Node:
def __init__(self,data):
self.data = data
self.left = None
self.right = None
self.count = 1
class BST:
def __init__(self):
self.root = None
self.size = 0
def _insert(self,val,node):
if val<node.data:
if node.left:
self._insert(val,node.left)
else:
self.size+=1
node.left = Node(val)
elif val>node.data:
if node.right:
self._insert(val,node.right)
else:
self.size+=1
node.right = Node(val)
else:
self.size+=1
node.count+=1
def insert(self,val):
if not self.root:
self.size+=1
self.root = Node(val)
else:
self._insert(val,self.root)
def _inorder(self,node):
if node:
self._inorder(node.left)
self.bstview.append(node.data)
self._inorder(node.right)
def view(self):
if self.root:
self.bstview = []
self._inorder(self.root)
return self.bstview
def _min(self,node):
if node.left:
return self._min(node.left)
return node
def _max(self,node):
if node.right:
return self._max(node.right)
return node
def min(self):
if self.root:
return self._min(self.root).data
def max(self):
if self.root:
return self._max(self.root).data
def _remove(self,val,node):
if not node:
return
if val < node.data:
node.left = self._remove(val,node.left)
elif val > node.data:
node.right = self._remove(val,node.right)
else:
if node.count >1:
self.size-=1
node.count-=1
return node
elif not node.left:
temp = node.right
del node
self.size-=1
return temp
elif not node.right:
temp = node.left
del node
self.size-=1
return temp
else:
temp = self._min(node.right)
node.data = temp.data
node.right = self._remove(temp.data,node.right)
return node #WHY THIS IS NECESSARY PLEASE EXPLAIN!!
def remove(self,val):
if self.root:
self._remove(val,self.root)
b = BST()
for x in list(map(int,"20 10 30 5 15 25 35 3 7 23 27 40 4 45".split(" "))):
b.insert(x)
print(b.view())
print(b.min(),b.max(),b.size)
b.remove(20)
b.remove(45)
b.remove(1)
print(b.view())
print(b.min(),b.max(),b.size)
In the above code of BST, why is it necessary to return node in the _remove() method? The if/elif statements are already returning nodes?
Also, I would be greatly thankful to you if you can guide me to an easy/less time consuming way to understand recursive functions (I know what they are and how they work, my question is if someone asks me how a particular recursive function works then I would be able to explain him without confusing myself).

binary search tree - recursive insertion python

I'm trying to correctly construct a binary search tree with a recursive insert function that will allow me to initialize a tree and continue to add nodes (stems). Here is the code that I've done so far (newer to coding, so probably way too wordy):
class Binary_Search_Tree:
class __BST_Node:
def __init__(self, value):
self.value = value
self.right_child = None
self.left_child = None
def __init__(self):
self.__root = None
self.__height = 0
self.value = None
def _recursive_insert(self, value):
new_stem = Binary_Search_Tree.__BST_Node(value)
if self.__root is None:
self.__root = new_stem
self.__root.value = new_stem.value
else:
if self.__root.value > new_stem.value:
if self.__root.right_child is None:
self.__root.right_child = new_stem
self.__root.right_child.value = new_stem.value
else:
self.__root.right_child._recursive_insert(self.__root, value)
else:
if self.__root.left_child is None:
self.__root.left_child = new_stem
self.__root.left_child.value = new_stem.value
else:
self.__root.left_child._recursive_insert(self.__root, value)
def insert_element(self, value):
element_to_insert = self._recursive_insert(value)
return element_to_insert
I then try to add values to this new tree in the main method:
if __name__ == '__main__':
new = Binary_Search_Tree()
new.insert_element(23)
new.insert_element(42)
new.insert_element(8)
new.insert_element(15)
new.insert_element(4)
new.insert_element(16)
The error that I keep getting is: '__BST_Node' object has no attribute '_recursive_insert' This pops up after inserting the first element, so I'm guessing the error occurs somewhere in the else statement. If anyone can figure out where my error is or has any tips on how to make this code more readable/user friendly, I'd be appreciative!
The issue is with this line:
self.__root.right_child._recursive_insert(self.__root, value)
As well as this:
self.__root.left_child._recursive_insert(self.__root, value)
You've defined _recursive_insert to be a method of Binary_Search_Tree, but you're calling it with an instance of __BST_Node, in self.__root.xxxxx_child._recursive_insert, where xxxx_child is an instance of BST_Node.
In fact, your entire _recursive_insert function leaves something to be desired. You're not returning anything from it, but you are assigning it's (non-existent) return value to something in insert_element.
Fixed code:
def _recursive_insert(self, root, value):
new_stem = Binary_Search_Tree.__BST_Node(value)
if root is None:
root = new_stem
else:
if root.value < new_stem.value:
if root.right_child is None:
root.right_child = new_stem
else:
root = self._recursive_insert(root.right_child, value)
elif root.value > new_stem.value:
if root.left_child is None:
root.left_child = new_stem
else:
root = self._recursive_insert(root.left_child, value)
return root
def insert_element(self, value):
self.__root = self._recursive_insert(self.__root, value)
return element_to_insert
Your function did not return an updated root
Your function does not handle a case when the value being inserted would be equal to an existing value, causing spurious entries.
With each recursive call, your function was being passed the same values, so it would not be able to know what to do. A new parameter for the node must be passed across calls. This makes it possible to establish a base case and return.

Function to Calculate the height of a Binary Search Tree is not working properly

I have tried various methods in order to calculate the height of a Binary Search Tree which includes recursion and also using a list in order to add the node along with it's depth.But for both the methods,the output is not correct.
Here's my code for the same:
class Node:
def __init__(self,data):
self.data=data
self.left=None
self.right=None
def Insert_BTreeNode(self,data):
if self.data:
if data<=self.data:
if self.left is None:
self.left=Node(data)
else:
self.left.Insert_BTreeNode(data)
elif data>self.data:
if self.right is None:
self.right=Node(data)
else:
self.right.Insert_BTreeNode(data)
else:
self.data=data
def Lookup(self,data,parent=None):
if data<self.data:
if self.left is None:
return None,None
return self.left.Lookup(data,self)
elif data>self.data:
if self.right is None:
return None,None
return self.right.Lookup(data,self)
else:
if (parent is not None):
print(self.data,parent.data)
return (self,parent)
def Children_count(self):
count=0
if self.left:
count+=1
if self.right:
count+=1
return (count)
def Delete(self,data):
children_count=0
node,parent=self.Lookup(data)
if node is not None:
children_count=node.Children_count()
if children_count==0:
if parent:
if parent.left is Node:
parent.left=None
else:
parent.right=None
del node
else:
self.data=data
elif children_count==1:
if node.left:
n=node.left
else:
n=node.right
if parent:
if parent.left is node:
parent.left=n
else:
parent.right=n
del node
else:
self.left=n.left
self.right=n.right
self.data=n.data
else:
parent=node
successor=node.right
while successor.left:
parent=successor
successor=successor.left
node.data=successor.data
if parent.left==successor:
parent.left=successor.right
else:
parent.right=successor.right
def print_treeInorder(self):
if self.left:
self.left.print_treeInorder()
print(self.data)
if self.right:
self.right.print_treeInorder()
def print_treePostorder(self):
if self.left:
self.left.print_treePostorder()
if self.right:
self.right.print_treePostorder()
print(self.data)
def height(self):
if self is None:
return 0
else:
return max(height(self.getLeft()), height(self.getRight()))+ 1
def print_treePreorder(self):
print(self.data)
if self.left:
self.left.print_treePreorder()
if self.right:
self.right.print_treePreorder()
def getLeft(self):
return self.left
def getRight(self):
return self.right
def maxDepth(self): #Level order Traversal
if self is None:
return 1
q=[]
q.append([self,1])
while(len(q))!=0:
node,temp=q.pop()
if node.getLeft()!=None:
q.append([node.getLeft(),temp+1])
if node.getRight()!=None:
q.append([node.getRight(),temp+1])
return temp
b_tree_input=list(map(int,input().split()))
root=Node(b_tree_input[0])
for i in range(1,len(b_tree_input)):
root.Insert_BTreeNode(b_tree_input[i])
print(root.height())
print(root.maxDepth())
For the sample input of 2,1,3,4.Both the function should return the answer as 3 but the height function returns the following.
NameError: name 'height' is not defined
While the maxDepth() function returns the answer as 2.
Regarding height: I'm not a python programmer, but shouldn't it be
max(self.getLeft().height(), self.getRight().height())
// instead of
max(height(self.getLeft()), height(self.getRight()))
since height is a member function?
However, this implies you have to check self.getLeft() and self.getRight() for None values before calling height. If you don't want to do the extra checks, think of making height a global function unrelated to your class, then your previous approach on recursion should work.
The height function with checks for None could look as follows (no guarantee on the syntax details):
def height(self):
if self is None:
return 0
elif self.left and self.right:
return max(self.left.height(), self.right.height()) + 1
elif self.left:
return self.left.height() + 1
elif self.right:
return self.right.height() + 1
else:
return 1
In maxDepth you process child nodes in a right-to-left DFS but you only take the result of the last processed path instead of comparing current temp to the maximum of already found depths.
So in your [2,1,3,4] example, the execution order is as follows:
q=[[2,1]] -> take [2,1] -> temp=1
q=[[1,2],[3,2]] -> take [3,2] -> temp=2
q=[[1,2],[4,3]] -> take [4,3] -> temp=3
q=[[1,2]] -> take [1,2] -> temp=2
end
I think you can now figure out how to change your algorithm.
Also you should consider to change the self is None case to return 0.

Resources