Deletion in BST (python) | unexpected additional deletions? - python

def delete(node, key):
if not node: return None
# Wrong node, search correct child
if key < node.data:
delete(node.left, key)
elif key > node.data:
delete(node.right, key)
# Correct node found
else:
#1. node has no children
if not (node.left and node.right): return None
#2. node has only left child
if node.left and not node.right: return node.left
#3. node has only right child
if not node.left and node.right: return node.right
#4. node has both left & right children
## Need to replace current value with next biggest value
## So go right once then all left to end
## Once this value is found, assign to appropriate position
## Then remove this val from its previous position
temp = node.right
while temp.left: temp = temp.left
node.data = temp.data
node.right = delete(node.right, temp.data)
t = BinaryTree([100, 50, 200, 25, 75, 350])
delete(t.root, 100)
I think that this BST deletion code mostly works, but it's a little buggy. If I delete the root node, 100, then 350 will be missing, following, given the BST, t = BinaryTree([100, 50, 200, 25, 75, 350]).
What is going on here? I'm not sure why 350 has been deleted in the process. I'm wondering if it's related to how I replace the node value upon successful deletion.
Optional but possibly helpful context
class BinaryTreeNode:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
class BinaryTree:
def __init__(self, *args):
if len(args) < 1:
self.root = None
elif isinstance(args[0], int):
self.root = BinaryTreeNode(args[0])
else:
self.root = None
for x in args[0]:
self.insert(x)
def insert(self, node_data):
new_node = BinaryTreeNode(node_data)
if not self.root:
self.root = new_node
else:
# root has no parent, so assign none for 1st iteration
parent = None
temp_pointer = self.root
while temp_pointer:
# update parent
parent = temp_pointer
#update temp_pointer to left or right child
if node_data <= temp_pointer.data:
temp_pointer = temp_pointer.left
else:
temp_pointer = temp_pointer.right
# eventually, temp_pointer will point to None, exiting while loop
# assign to left or right child as appropriate
if node_data <= parent.data:
parent.left = new_node
else:
parent.right = new_node

There are a few issues:
As delete is designed to return a node reference or None, you should make sure not to ignore that returned reference. You did it right near the end of your function (node.right = delete(node.right, temp.data)), but elsewhere delete is called without regards of the returned reference. So:
The initial call in the main program should look like this:
t.root = delete(t.root, 100)
This will ensure that the root attribute is set to None when the last node has been deleted from the tree.
The recursive call in the first if block should be:
node.left = delete(node.left, key)
And similarly in the second block:
node.right = delete(node.right, key)
The function delete should always return a node reference after a recursive call has been made, yet this is missing in many of your cases, so add at the very bottom of your function a kind of "catch all" and return the current reference you have:
return node
The condition for identifying a leaf node is wrong. The and should be a or:
if not (node.left or node.right): return None
The corrected code -- comments indicate changes:
def delete(node, key):
if not node: return None
if key < node.data:
node.left = delete(node.left, key) # assign back!
elif key > node.data:
node.right = delete(node.right, key) # assign back!
else:
if not (node.left or node.right): return None # condition corrected
if node.left and not node.right: return node.left
if not node.left and node.right: return node.right
temp = node.right
while temp.left: temp = temp.left
node.data = temp.data
node.right = delete(node.right, temp.data)
return node # always return a node when a recursive call was made
t = BinaryTree([100, 50, 200, 150, 175, 25, 75, 350])
t.root = delete(t.root, 350) # assign back!
Considerations
Not a problem in the algorithm, but it is a good habit to put the body of an if or while statement on the next line, indented
This function would better be a method on the BinaryTree class -- then the main program should not have to worry about getting/setting the root attribute -- and most of the function's (recursive) logic could be implemented as a method on the BinaryTreeNode class.

Related

How to fix 'NoneType' has no attribute 'key', when trying to compare a key value to a string

I am writing a program where the user inputs a postfix expression and it outputs the answer. Current I am stuck when Using my 'evaluate' function within my for loop.
Inside my For loop Main.py:
else:
# Debug Code
print('{}: Else'.format(i))
print('{}: Length'.format(len(stack)))
Node.right = stack.pop()
Node.left = stack.pop()
Node = TreeNode(str(i))
stack.push(str(i))
# Debug Code
print('{}: Right Key'.format(Node.right))
print('{}: Left Key'.format(Node.left))
print('{}: Node Key'.format(Node.key))
print('{}: Node Key Type'.format(type(Node.key)))
Node = evaluate(Node)
stack.push(int(Node))
I am getting the error below:
Traceback (most recent call last):
File "c:\Users\dpr48\main.py", line 49, in <module>
Node = evaluate(Node)
File "c:\Users\dpr48\main.py", line 10, in evaluate
return evaluate(node.left) + evaluate(node.right)
File "c:\Users\dpr48\main.py", line 9, in evaluate
if node.key == '+':
AttributeError: 'NoneType' object has no attribute 'key'
So my question is why is it not using the 'TreeNode' class to get the key value? As well as the line of code that should define the 'Node.left' as the 'stack.pop()' value and 'Node.right' as the 'stack.pop()' value ends up not changing either of them and leaves them as None, as found in the 'Debug Code' that I have implemented to see what the program is doing interenally.
Provided each class used below:
Main.py
from Stack import Stack
from TreeNode import TreeNode
def evaluate(node):
if node.key == '+':
return evaluate(node.left) + evaluate(node.right)
elif node.key == '-':
return evaluate(node.left) - evaluate(node.right)
elif node.key == '*':
return evaluate(node.left) * evaluate(node.right)
elif node.key == '/':
return evaluate(node.left) / evaluate(node.right)
else:
return node.key
stack = Stack()
exp = "23+"
list = [*exp]
for i in list:
if i.isdigit() is True:
# Debug Code
print('{}: True'.format(i))
Node = TreeNode(int(i))
stack.push(int(i))
else:
# Debug Code
print('{}: Else'.format(i))
print('{}: Length'.format(len(stack)))
Node.right = stack.pop()
Node.left = stack.pop()
Node = TreeNode(str(i))
stack.push(str(i))
# Debug Code
print('{}: Right Key'.format(Node.right))
print('{}: Left Key'.format(Node.left))
print('{}: Node Key'.format(Node.key))
print('{}: Node Key Type'.format(type(Node.key)))
Node = evaluate(Node)
stack.push(int(Node))
print(evaluate(stack.node))
Stack.py
from Node import Node
from LinkedList import LinkedList
class Stack:
def __init__(self):
self.list = LinkedList()
def push(self, new_item):
# Create a new node to hold the item
new_node = Node(new_item)
# Insert the node as the list head (top of stack)
self.list.prepend(new_node)
def pop(self):
# Copy data from list's head node (stack's top node)
popped_item = self.list.head.data
# Remove list head
self.list.remove_after(None)
# Return the popped item
return popped_item
def __len__(self):
node = self.list.head # Start at head of stack to count until stack returns Null
count = 0
while node != None:
node = node.next
count+=1
return count # Returning length of stack
LinkedList.py
class LinkedList:
def __init__(self):
self.head = None
self.tail = None
def append(self, new_node):
if self.head == None:
self.head = new_node
self.tail = new_node
else:
self.tail.next = new_node
self.tail = new_node
def prepend(self, new_node):
if self.head == None:
self.head = new_node
self.tail = new_node
else:
new_node.next = self.head
self.head = new_node
def insert_after(self, current_node, new_node):
if self.head == None:
self.head = new_node
self.tail = new_node
elif current_node is self.tail:
self.tail.next = new_node
self.tail = new_node
else:
new_node.next = current_node.next
current_node.next = new_node
def remove_after(self, current_node):
# Special case, remove head
if (current_node == None) and (self.head != None):
succeeding_node = self.head.next
self.head = succeeding_node
if succeeding_node == None: # Remove last item
self.tail = None
elif current_node.next != None:
succeeding_node = current_node.next.next
current_node.next = succeeding_node
if succeeding_node == None: # Remove tail
self.tail = current_node
Node.py
class Node:
def __init__(self, initial_data):
self.data = initial_data
self.next = None
TreeNode.py
class TreeNode:
# Constructor assigns the given key, with left and right
# children assigned with None.
def __init__(self, key):
self.key = key
self.left = None
self.right = None
There are several issues:
Node is the name of a class, yet you use the same name for a TreeNode instance, shadowing the class name. This is not the main problem, but certainly not advised. Related: Don't use PascalCase for instances, but camelCase. So node, not Node.
You assign to Node.right when you have not yet defined Node yet, which happens later with Node = TreeNode(str(i)). You should first assign to Node (well, better node) and only then assign to its attributes.
With Node.right = stack.pop() you clearly expect the stack to contain TreeNode instances, but with stack.push(str(i)) you push strings. That will lead to the problems you describe. The stack should not be populated with strings, but with TreeNode objects.
At the end of the else block you call evaluate, and then push that result value to the stack. This is wrong and should be removed. The evaluation should only happen when you have completed the tree, and it should not involve the stack. The stack has a role in building the tree, not in evaluating it.
The final print line makes an access to stack.node, but stack has no node attribute. You'll want to pop the top item from the stack, which (if the input syntax was correct) should only have 1 node left on it, representing the root of the tree.
Not a problem, but i is guaranteed to be a string (with length 1), so there is no need to call str on it.
Here is the corrected code:
for i in list:
if i.isdigit() is True:
node = TreeNode(int(i)) # lowercase name
stack.push(node) # don't push string, but object
else:
node = TreeNode(i) # First create the node
node.right = stack.pop() # Then assign to its attributes
node.left = stack.pop()
stack.push(node) # don't push string
# Don't evaluate here, nor push anything else to the stack
print(evaluate(stack.pop()))

how to update class attribute value from method

I am trying to implement a Binary Tree using a queue. I am having an issue to set a new value for self.root who is initially set in class BinaryTree as None using the method InsertNode(self,data). I try to return self.root with it's new value but the value remains the same (None) Any Idea?
class BinaryTree:
def __init__(self):
self.root = None
def InsertNode(self, data):
newNode = Node(data)
newNode = newNode.data
print('self.root =', self.root)
print('new node', newNode)
if self.root == None:
self.root = newNode
print('self.root =', self.root)
else:
print('else won')
queue = []
# print(queue)
queue.append(self.root)
while True:
node = queue.pop(0)
if node.left != None and node.right != None:
queue.append(node.left)
queue.append(node.right)
else:
if node.left == None:
node.left = newNode
queue.append(node.left)
else:
if node.right == None:
node.right = newNode
queue.append(node.right)
break
return self.root
Some issues:
Right after creating a new node and assigning its reference to newNode, your code assigns the data of that node to newNode. At that point you lose the reference to your node, and it is forever lost. Moreover, while self.root is supposed to be node reference, you then continue to assign the data value to self.root. In short, don't do newNode = newNode.data
The while loop can only make one iteration, because it has an unconditional break. That break should be conditional: it should be indented one level more so it is placed in the else block.
With those two fixes, your code will work (provided the other code you have, like for class Node is error-free).
Not a problem, but you should also look at these points:
At the place where you have if node.right == None, this condition is always going to be true, given that it was already known that not both node.left and node.right are None, and also that node.left is None. So the only remaining possibility for node.right is that it is None.
Once you have assigned newNode to where it belongs, there is no need any more to append to the queue.
When using a list as a FIFO queue, it is better to use a deque and its popleft method.
In Python, names of methods are commonly written with a first lowercase letter. Either name your method insertNode or why not just insert?
As node.left is a node reference or None, you can just test for node.left instead of node.left != None. Similarly for node.right
Instead of testing for the end-condition inside the while True loop, you could change the order of your code a bit and make it a while node.left and node.right loop. Then the assignment of newNode to the right attribute can happen after the loop has finished.
The insert method does not need to return the root node. The reference of the root node is maintained in an attribute of the instance. The outside caller should in fact have no need to ever access node references. I would suggest not to return the root node.
Here is the resulting code. I added __repr__ implementations so the tree can be printed with some basic indentation format:
from collections import deque
class Node:
def __init__(self, data):
self.data = data
self.left = self.right = None
def __repr__(self, tab=""):
return ((self.right.__repr__(" " + tab) if self.right else "")
+ tab + repr(self.data) + "\n"
+ (self.left.__repr__(" " + tab) if self.left else ""))
class BinaryTree:
def __init__(self):
self.root = None
def insert(self, data):
newNode = Node(data)
node = self.root
if not node:
self.root = newNode
else:
queue = deque()
while node.left and node.right:
queue.append(node.left)
queue.append(node.right)
node = queue.popleft()
if node.left:
node.right = newNode
else:
node.left = newNode
def __repr__(self):
return repr(self.root).rstrip() if self.root else "<Empty>"
# Example run
tree = BinaryTree()
print(tree)
for i in range(1, 13):
tree.insert(i)
print(tree)

Can't understand this Binary Search Tree (BST) algorithm for Insertion of value in Python

I am learning algorithms. For the BST Construction to insert a value in the BST, I came across this code. I am not that good at OOPS concept and can't figure out how currentnode.left = BST(value) and currentnode = currentnode.left works. If someone can help me understand this, it would be of great help.
class BST:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def insert(self, value):
currentnode = self
while True:
if value < currentnode.value:
if currentnode.left is None:
currentnode.left = BST(value)
break
else:
currentnode = currentnode.left
else:
if currentnode.right is None:
currentnode.right = BST(value)
break
else:
currentnode = currentnode.right
return self
In the insert function, the currentnode has been assigned to self. Starting with the while loop, the parameter value is checked with currentnode's value. If it is small, the first condition is executed else second.
Now comes your doubt.
Let's say the first condition is being executed. If the currentnode's left value is none, then the code calls BST(value) i.e. the constructor is called which initiates a new node, which in turn becomes the currentnode's left child.
Else, if there is already a left child, that child becomes the currentnode and the while loop is iterated again and again until a suitable, position is found.
Also, If this code seems complicated. You should refer this, just in case if it helps.
class Node:
def __init__(self,key):
self.left = None
self.right = None
self.val = key
def insert(root,node):
if root is None:
root = node
else:
if root.val < node.val:
if root.right is None:
root.right = node
else:
insert(root.right, node)
else:
if root.left is None:
root.left = node
else:
insert(root.left, node)
# 5
# / \
# 3 7
# / \ / \
# 2 4 6 8
r = Node(5)
insert(r,Node(3))
insert(r,Node(2))
insert(r,Node(4))
insert(r,Node(7))
insert(r,Node(6))
insert(r,Node(8))

Remove recursively from a BST

I am finished with the case when the node that i want to remove is the root node or the leaf node, but i need to be able to remove also when it has siblings or children, which i am finding very hard.
class Node:
def __init__(self, key=None, data=None):
self.key = key
self.data = data
self.left = None
self.right = None
class BST:
def __init__(self):
self.root = None
self.size = 0
def remove(self, key):
self.root = self._remove(key, self.root)
def insert(self, key, data):
self.root = self._insert(self.root, key, data)
def _insert(self, root, key, data):
if root == None:
self.size += 1
return Node(key, data)
if root.key == key:
return root
elif root.key > key:
root.left = self._insert(root.left, key, data)
else:
root.right = self._insert(root.right, key, data)
return root
def _remove(self, key, node):
if node == None:
return None
if key == node.key:
if node.left != None and node.right == None: # if trying to remove root and right side is empty
return node.left
elif node.left == None and node.right != None: # if trying to remove root and left side is empty
return node.right
elif node.left == None and node.right == None: # if trying to remove leaf
return node
# two more cases to check when it has siblings
# iterates recursively in the bst
elif key <= node.key:
node.left = self._remove(key, node.left)
else:
node.right = self._remove(key, node.right)
return node
I posted the whole code so if anyone wants to test in their machine is welcome to do so, or someone can use it for educational purpose.
In the future try to perform some debugging and provide some sample output and expected output.
Consider below
def _remove(self, key, node):
if node == None:
return None
if key == node.key:
if node.left and not node.right: # only left
return node.left
elif node.right and not node.left: # only right
return node.right
elif not node.right and not node.left: # neither
return None
else : # both
inorder_successor = node.right
while inorder_successor.left:
inorder_successor = inorder_successor.left
# remember to replace inorder_successor with it's right child
...
...
return inorder_successor
# iterates recursively in the bst
elif key <= node.key:
node.left = self._remove(key, node.left)
else:
node.right = self._remove(key, node.right)
return node
A few observations about what changed
You check for None using, is != None which is a very non Pythonic way to it. Just check for is and is not instead
The right way to replace a node in a BST that has both children is with the inorder successor (left most descendant of the right child of the deleted node)

Python Binary Search Tree Delete Function

class Node:
def __init__(self, val):
self.val = val
self.left = None
self.right = None
class BST:
def __init__(self, root=None):
self.root = root
def remove(self, val):
if self.root == None:
return root
else:
self._remove(val, self.root)
def _remove(self, val, node):
if node == None:
return node # Item not found
if val < node.val:
self._remove(val, node.left)
elif val > node.val:
self._remove(val, node.right)
else:
# FOUND NODE TO REMOVE
if node.left != None and node.right != None: # IF TWO CHILDREN
node.val = self._find_min(node.right)
node.right = self._remove(node.val, node.right)
else: # ZERO OR ONE CHILD
if node.left == None: # COVERS ZERO CHILD CASE
node = node.right
elif node.right == None:
node = node.left
return node
Cannot figure out why this function will not delete some values. I debugged with print statements and can see that if I try to remove a value, the function will enter the else block and successfully remove a node with two children. However, when attempting to remove a node with one or zero children, the codes executes with no errors, but when I print the tree to view its contents the node is still there.
The node to be removed will have at least one None child, and it seems straightforward to set the node equal to its right (or left) child, which I assume sets the node to None.
I have some experience with Java, but fairly new to Python, and I sometimes run into trouble with the "self" protocol, but I don't think that is the case here.

Categories