Find parent in Binary Search Tree? - python

I wish to find the parent to a node with a certain value in a BST. My node class has attributes item (i.e the value/key), left and right.
The idea to find a parent is like this:
1) If the value (key) does not exist, return None, None
2) If the root is equal to the value (key) then return None, root
3) Else find the value (key) and return (par, node) where par is the parent and node
My function looks like this:
def findpar(self,key):
if not self._exists(key):
return None,None
elif self.root.item==key:
return None, self.root
p=self.root
found=False
while not found:
if p.left.item==key:
return p, p.left
found=True
elif p.right.item==key:
return p, p.right
found=True
elif p.item<key:
p=p.left
elif p.item>key:
p=p.right
How do I handle the issue when p.left or p.right is None?

As your code currently works, it is impossible that you're turning toward a None left or right child. This is because your code starts with
if not self._exists(key):
return None,None
So key must exist, and if it must exist, it must exist on the search path.
It should be noted that you're effectively performing the search twice, though, which is not that efficient. Instead, you could try something like this:
def findpar(self,key):
parent, node = None, self.root
while True:
if node is None:
return (None, None)
if node.item == key:
return (parent, node)
parent, node = node, node.left if key < node.item else node, node.right

Related

Issues with replacing leafs in BST implementation

I'm working on a project where we are supposed to implement a BST via dataclass. The first "part" of the code works. It sorts 6 entries (key/value combo) according to size. However, the next step is to replace some values in the BST. I'm almost certain that the best way to do this is by checking the entire tree for key matches and then replacing the value of said key with the new value. However, the code only "semi" works.
#dataclass
class Node:
key: Any = None # the key
value: Any = None # the value
left: Any = None # left child (a Node)
right: Any = None # right child (a Node)
def put(self, key, value):
new_leaf = Node(key, value) # Below SHOULD check if there is a key match in either the
#left or right leaf.
if self.left or self.right is not None:
if self.left is not None: #Checks left leaf for key match.
if self.left.key == new_leaf.key:
print("Match on SELF.LEFT.KEY:", self.left.key, new_leaf.key)
if self.right is not None: #Checks right leaf for key match.
if self.right.key == new_leaf.key:
print("Match on SELF.RIGHT.KEY:", self.right.key, new_leaf.key)
if new_leaf.value < self.value: #Adds new leafs normally. This part (should) work(?).
if self.left is None:
self.left = Node(key, value, None, None)
else:
self.left.put(key, value)
if new_leaf.value > self.value:
if self.right is None:
self.right = Node(key, value, None, None)
else:
self.right.put(key, value)
Here is the original output (when just adding key/values):
{ (Adam|27) (Ceve|37) (Ella|39) (Owen|40) (Zoe|41) (Fred|44)}
And here is the output when I try to replace key Ceve and key Zoe by adding them to the put method (Match on SELF.LEFT.KEY means that an identical key has been found in the tree and is to be replaced (later)):
Override existing values
Match on SELF.LEFT.KEY: Zoe Zoe
{ (Adam|27) (Ceve|37) (Ella|39) (Owen|40) (Zoe|41) (Fred|44) (Zoe|99) (Ceve|100)}
Now, it should print two "matches" but for some reason it doesn't detect Ceve. I've messed around with print functions to try and actually see whats going on and it seems to be that it never actually finds the Ceve leaf (obviously), but I don't know why that is.
Does anyone have any pointers as to what could be causing the issue?

Removing a node from binary tree

I'm currently working on leetcode problem 366 where we have to find list of lists that contains values of leaves of each generation. I wanted to achieve this by recursion where if a node does not have left or right child, the value is recorded then the node removed by setting it to None. Here is my code:
def findLeaves(self, root: Optional[TreeNode]) -> List[List[int]]:
leaf_list = []
sub_list = []
def traverse(node):
if node == None:
return
if node.left == None and node.right == None:
sub_list.append(node.val)
node = None
return
traverse(node.left)
traverse(node.right)
return root
while True:
if root == None:
break
sub_list = []
traverse(root)
leaf_list.append(sub_list)
print(leaf_list)
return leaf_list
The problem seems to be that when a certain node is set to None, that change isn't retained. Why is it that I can't set a node to None to remove it?
Thanks
The tree can only be mutated when you assign to one if its node's attributes. An assignment to a variable, only changes what the variable represents. Such assignment never impacts whatever previous value that variable had. Assigning to a variable is like switching from one value to another without affecting any data structure. So you need to adapt your code such that the assignment of None is done to a left or right attribute.
The exception is for the root node itself. When the root is a leaf, then there is no parent to mutate. You will then just discard the tree and switch to an empty one (None).
One way to achieve this, is to use the return value of traverse to update the child-reference (left or right) that the caller of traverse needs to update.
Here is your code with those adaptations:
def findLeaves(root):
sub_list = []
def traverse(node):
if not node:
return
if not node.left and not node.right:
sub_list.append(node.val)
return # By returning None, the parent can remove it
node.left = traverse(node.left) # Assign the returned node reference
node.right = traverse(node.right)
return node # Return the node (parent does not need to remove it)
leaf_list = []
while root:
sub_list = []
root = traverse(root)
leaf_list.append(sub_list)
return leaf_list

How to fix NoneType error in Python Binary Search Tree?

I'm creating a basic binary search tree program where the nodes have to be strings however, I keep getting this error:
'builtins.AttributeError: 'NoneType' object has no attribute 'addNode'
I'm a bit confused because I thought you had to declare the children nodes as None. My code is below (please excuse the messiness and extra print statements):
class BinarySearchTree:
#constructor with insertion value & left/right nodes as None
def __init__(self, data):
self.data = data
self.left = None
self.right = None
#function to insert node
def addNode(root, data):
#when tree doesn't exist
if root == None:
return BinarySearchTree(data)
else:
#when node is already in tree
if root.data == data:
return root
#smaller values go left
elif data < root.data:
root.left.addNode(data)
#bigger values go right
else:
root.right.addNode(data)
return root
#function to find smallest node value (to help with deletion)
def smallestNode(root):
node = root
#loop goes to lowest left leaf
while(node.left is not None):
node = node.left
return node
#function to delete node
def removeNode(root, data):
if root == None:
return root
#when node to be deleted in smaller than root, go left
if data < root.data:
root.left = root.left.removeNode(data)
#when node to be deleted in bigger than root, go right
elif data > root.data:
root.right = root.right.removeNode(data)
##when node to be deleted in the same as root...
else:
#when node has only 1 or 0 children
if root.right == None:
move = root.left
root = None
return move
elif root.left == None:
move = root.right
root = None
return move
#when node has 2 children, copy then delete smallest node
move = root.right.smallestNode()
root.data = move.data
root.right = root.right.removeNode(move.data)
return root
def findNode(root, data):
#if current node is equal to data value then return the root
if root.data == data or root == None:
return root
#if current node is greater than the data value then, search to the left
elif data < root.data:
return root.left.findNode(data)
#if current node is less than the data value then, search to the right
else:
return root.right.findNode(data)
def createBST(keys):
root = BinarySearchTree(keys[0])
for i in range(1,len(keys)):
root.addNode(keys[i])
return root
print('Hi! Welcome to the Binary Search Tree Builder')
print('Here are your options below:')
print('1) Build new tree')
print('2) Add a node')
print('3) Find a node')
print('4) Delete a node')
choice = int(input('What would you like to do?: '))
if choice == 1:
nodes = list(input("Enter the strings you would like to build your tree from (separate by a space): ").split())
print(nodes)
tree = createBST(nodes)
print(tree)
I'm wondering where exactly is this error coming from and how can I fix it? Also, if you see any other problems occuring in my code, please let me know!
You have conflated the notion of "tree node" and "tree". What you have there is a mixture of the two. Remember, the first parameter to ALL member functions should be "self". Replacing "root" by "self" might make your problem more clear.
So, you might create a BinaryTree class, which has a single member called self.root that holds a BinaryTreeNode object, once you have a node. The nodes will hold the left and right values, each of which will either have None or a BinaryTreeNode object. The node class probably doesn't need much code -- just self.left and self.right.
The BinaryTree class will have an addNode member that knows how to traverse the tree to find the right spot, but when you find the spot, you just set self.left = BinaryTreeNode(...). A node does not know about the tree, so a node does not know how to add new nodes. That's a function of the tree itself.
Does that make your path forward more clear?

Binary Tree: How Do Class Instances Link?

I am trying to understand binary trees, but doing so has brought me to confusion about how class instances interact, how does each instance link to another?
My Implementation:
class Node(object):
def __init__(self, key):
self.key= key
self.L = None
self.R = None
class BinaryTree(object):
def __init__(self):
self.root = None
def get_root(self):
return self.root
def insert(self, key):
if self.get_root()==None:
self.root = Node(key)
else:
self._insert(key, self.root)
def _insert(self, key, node):
if key < node.key:
if node.L == None:
node.L = key
else:
self._insert(key, Node(node.L))
if key > node.key:
if node.R == None:
node.R = key
else:
self._insert(key, Node(node.R))
myTree= BinaryTree()
A Scenario
So lets say I want to insert 10, I do myTree.insert(10) and this will instantiate a new instance of Node(), this is clear to me.
Now I want to add 11, I would expect this to become the right node of the root node; i.e it will be stored in the attribute R of the root node Node().
Now here comes the part I don't understand. When I add 12, it should become the child of the root nodes right child. In my code this creates a new instance of Node() where 11 should the be key and 12 should be R.
So my question is 2-fold: what happens to the last instance of Node()? Is it deleted if not how do I access it?
Or is the structure of a binary tree to abstract to think of each Node() connected together like in a graph
NB: this implementation is heavily derived from djra's implementation from this question How to Implement a Binary Tree?
Make L and R Nodes instead of ints. You can do this by changing the parts of your _insert function from this:
if node.L == None:
node.L = key
to this:
if node.L == None:
node.L = Node(key)
There is also a problem with this line:
self._insert(key, Node(node.L))
The way you're doing it right now, there is no way to access that last reference of Node() because your _insert function inserted it under an anonymously constructed node that has no parent node, and therefore is not a part of your tree. That node being passed in to your insert function is not the L or R of any other node in the tree, so you're not actually adding anything to the tree with this.
Now that we changed the Ls and Rs to be Nodes, you have a way to pass in a node that's part of the tree into the insert function:
self._insert(key, node.L)
Now you're passing the node's left child into the recursive insert, which by the looks of thing is what you were originally trying to do.
Once you make these changes in your code for both the L and R insert cases you can get to the last instance of Node() in your
10
\
11
\
12
example tree via myTree.root.R.R. You can get its key via myTree.root.R.R.key, which equals 12.
Most of you're questions come from not finishing the program; In your current code after myTree.insert(11) you're tree is setting R equal to a int rather than another Node.
If the value isn't found then create the new node at that point. Otherwise pass the next node into the recursive function to keep moving further down the tree.
def _insert(self, key, node):
if key < node.key:
if node.L == None:
node.L = Node(key)
else:
self._insert(key, node.L)
if key > node.key:
if node.R == None:
node.R = Node(key)
else:
self._insert(key, node.R)
P.S. This isn't finished you're going to need another level of logic testing incase something is bigger than the current Node.key but smaller than the next Node.

How python pass by reference?

I tried to implement delete a node in a BST.
And here is my partial code.
def delete(node,key):
#Locate that node with value k
cNode=node
target=None
while cNode:
if cNode.value==key:
target=cNode
break
elif node.value>key:
cNode=cNode.lChild
elif node.value<key:
cNode=cNode.rChild
target=None
return node
When I tried to use the above method to delete a leaf node. I failed. when the method return, it did nothing to original BST. So what's the problem of this code? I assume it should have something about how python pass arguments by reference? But I am confused now.
Many thanks in advance.
target = None only rebinds the variable target to a new value, None. Whatever target was bound to before doesn't change.
You'll have to track the parent node and set it's lChild or rChild attribute to None instead.
def delete(node,key):
cNode = node
target = parent = None
while cNode:
if cNode.value == key:
target = cNode
break
elif cNode.value > key:
parent, cNode = cNode, cNode.lChild
elif cNode.value < key:
parent, cNode = cNode, cNode.rChild
if target:
if parent:
if parent.lChild is target:
parent.lChild = None
else:
parent.rChild = None
else:
# target is top-level node; perhaps return None in that case?
return node

Categories

Resources