Inserting a node in Binary search tree in python - python

I know the basic working code for inserting a node in BST. But I expected this function to add a node at the right end of BST (because its value is the maximum in that tree), but it doesn't work and I wanna know the reason. I tried debugging it but I'm still not clear why it's behaving this way.
def putValueInBST(root, val): # assuming the val = 7 which is max of all the existing node values in BST
if root is None:
root = Node(val)
elif val > root.data:
putValueInBST(root.right, val)
else:
putValueInBST(root.left, val)
This code below behaves as expected.
def put_val_manually(r, val):
r.right.right = Node(val)
Aren't both of the above functions kinda similar, since they are adding the node at the end of the BST?
(of course in the put_val_manually() function, I am doing it directly.)
The full code is here: https://i.ibb.co/yf2YTYy/code.png

Try to return the node
def putValueInBST(root, val): # assuming the val = 7 which is max of all the existing node values in BST
if root is None:
return Node(val)
elif val > root.data:
root.right=putValueInBST(root.right, val)
else:
root.left=putValueInBST(root.left, val)
root=putValueInBST(root,val)
This code works

Related

How to index each node in O(1) complexity and update all indexes each time when doing insertion/deletion/rotations in a Balanced AVL tree

The solution I have now is something of an in order traversal:
def update_index(self, current: AVLTreeNode) -> None:
"""
Call updated_index_aux to traverse the tree in order and update the index appropriately
"""
# if current is not None:
# return current.index
# return 0
self.index = 0
self.update_index_aux(current)
def update_index_aux(self, current: AVLTreeNode) -> None:
if current is not None: #Base case if current is None
self.update_index_aux(current.left)
current.index = self.index
self.index += 1
self.update_index_aux(current.right)
but this requires me to call this method each time i insert/delete which is not efficient, how do i make it O(1) such that everything is updated each time i insert/delete or perform a rotation. It seems similar to get_height() method in an AVL tree which is O(1) so this is called on insert and delete:
# Update height of current node
current.height = max(self.get_height(current.left),
self.get_height(current.right)) + 1
but im not sure how to take these solutions to make it O(1) so i dont have to traverse the tree each time i insert or delete.
my get_height function:
def get_height(self, current: AVLTreeNode) -> int:
if current is not None:
return current.height
return 0
Below is my insert function into a AVL tree for reference:
def insert_aux(self, current: AVLTreeNode, key: K, item: I) -> AVLTreeNode:
"""
Attempts to insert an item into the tree, it uses the Key to insert it
"""
if current is None: # base case: at the leaf
current = AVLTreeNode(key, item)
self.length += 1
elif key < current.key:
current.left = self.insert_aux(current.left, key, item)
elif key > current.key:
current.right = self.insert_aux(current.right, key, item)
else: # key == current.key
raise ValueError('Inserting duplicate item')
current.height = max(self.get_height(current.left), self.get_height(current.right)) + 1 #Update height of current node
current = self.rebalance(current) #Rebalance tree after insertion if needed
return current
The reason i need to index each node is because I need to code a function that finds and gets a range of items stored in the AVL tree based on a range of indexes. I did that part already but I can only do it when the nodes are already indexed and the only way i can think of to index the nodes is in order traversal to each node and updating the index accordingly but I cant do it this way so Im trying to think of a way to update each index during insertion/deletion/rotations without needing to traverse each node and update

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.

Creating Binary Tree

Most of the questions I've searched for regarding binary trees shows the implementation of binary search trees, but not binary trees. The terms of a complete binary tree are:
Either an empty tree or it has 1 node with 2 children, where each
child is another Binary Tree.
All levels are full (except for possibly the last level)
All leaves on the bottom-most level are
as far left as possible.
I've come up with a concept but it doesn't seem to running through the recursion properly -- Does anyone know what I'm doing wrong?
class Node():
def __init__(self, key):
self.key = key
self.left = None
self.right = None
def add(self, key):
if self.key:
if self.left is None:
self.left = Node(key)
else:
self.left.add(key)
if self.right is None:
self.right = Node(key)
else:
self.right.add(key)
else:
self.key = key
return (self.key)
The problem in your code is that you are adding the same value multiple times. You add the node, and then still recurse deeper, where you do the same.
The deeper problem is that you don't really know where to insert the node before you have reached the bottom level of the tree, and have detected where that level is incomplete. Finding the correct insertion point may need a traversal through the whole tree... which is defeating the speed gain you would expect to get from using binary trees in the first place.
I provide here three solutions, starting with the most efficient:
1. Using a list as tree implementation
For complete trees there is a special consideration to make: if you number the nodes by level, starting with 0 for the root, and within each level from left to right, you notice that the number of a node's parent is (k-1)/2 when its own number is k. In the other direction: if a node with number k has children, then its left child has number k*2+1, and the right child has a number that is one greater.
Because the tree is complete, there will never be gaps in this numbering, and so you could store the nodes in a list, and use the indexes of that list for the node numbering. Adding a node to the tree now simply means you append it to that list. Instead of a Node object, you just have the tree list, and the index in that list is your node reference.
Here is an implementation:
class CompleteTree(list):
def add(self, key):
self.append(key)
return len(self) - 1
def left(self, i):
return i * 2 + 1 if i * 2 + 1 < len(self) else -1
def right(self, i):
return i * 2 + 2 if i * 2 + 2 < len(self) else -1
#staticmethod
def parent(i):
return (i - 1) // 2
def swapwithparent(self, i):
if i > 0:
p = self.parent(i)
self[p], self[i] = self[i], self[p]
def inorder(self, i=0):
left = self.left(i)
right = self.right(i)
if left >= 0:
yield from self.inorder(left)
yield i
if right >= 0:
yield from self.inorder(right)
#staticmethod
def depth(i):
return (i + 1).bit_length() - 1
Here is a demo that creates your example tree, and then prints the keys visited in an in-order traversal, indented by their depth in the tree:
tree = CompleteTree()
tree.add(1)
tree.add(2)
tree.add(3)
tree.add(4)
tree.add(5)
for node in tree.inorder():
print(" " * tree.depth(node), tree[node])
Of course, this means you have to reference nodes a bit different from when you would use a real Node class, but the efficiency gain pays off.
2. Using an extra property
If you know how many nodes there are in a (sub)tree, then from the bit representation of that number, you can know where exactly the next node should be added.
For instance, in your example tree you have 5 nodes. Imagine you want to add a 6 to that tree. The root node would tell you that you currently have 5 and so you need to update it to 6. In binary that is 110. Ignoring the left-most 1-bit, the rest of the bits tell you whether to go left or right. In this case, you should go right (1) and then finally left (0), creating the node in that direction. You can do this iteratively or recursively.
Here is an implementation with recursion:
class Node():
def __init__(self, key):
self.key = key
self.left = None
self.right = None
self.count = 1
def add(self, key):
self.count += 1
if self.left is None:
self.left = Node(key)
elif self.right is None:
self.right = Node(key)
# extract from the count the second-most significant bit:
elif self.count & (1 << (self.count.bit_length() - 2)):
self.right.add(key)
else:
self.left.add(key)
def inorder(self):
if self.left:
yield from self.left.inorder()
yield self
if self.right:
yield from self.right.inorder()
tree = Node(1)
tree.add(2)
tree.add(3)
tree.add(4)
tree.add(5)
for node in tree.inorder():
print(node.key)
3. Without extra property
If no property can be added to Node objects, then a more extensive search is needed to find the right insertion point:
class Node():
def __init__(self, key):
self.key = key
self.left = None
self.right = None
def newparent(self):
# Finds the node that should serve as parent for a new node
# It returns a tuple:
# if parent found: [-1, parent for new node]
# if not found: [height, left-most leaf]
# In the latter case, the subtree is perfect, and its left-most
# leaf is the node to be used, unless self is a right child
# and its sibling has the insertion point.
if self.right:
right = self.right.newparent()
if right[0] == -1: # found inbalance
return right
left = self.left.newparent()
if left[0] == -1: # found inbalance
return left
if left[0] != right[0]:
return [-1, right[1]] # found inbalance
# temporary result in perfect subtree
return [left[0]+1, left[1]]
elif self.left:
return [-1, self] # found inbalance
# temporary result for leaf
return [0, self]
def add(self, key):
_, parent = self.newparent()
if not parent.left:
parent.left = Node(key)
else:
parent.right = Node(key)
def __repr__(self):
s = ""
if self.left:
s += str(self.left).replace("\n", "\n ")
s += "\n" + str(self.key)
if self.right:
s += str(self.right).replace("\n", "\n ")
return s
tree = Node(1)
tree.add(2)
tree.add(3)
tree.add(4)
tree.add(5)
print(tree)
This searches recursively the tree from right to left, to find the candidate parent of the node to be added.
For large trees, this can be improved a bit, by doing a binary-search among paths from root to leaf, based on the length of those paths. But it will still not be as efficient as the first two solutions.
You can use the sklearn Decision trees, as they are able to be set up as binary decision trees as well. link to the documentation here.
You really need to augment your tree in some way. Since this is not a binary search tree, the only real information you have about each node is whether or not it has a left and right child. Unfortunately, this isn't helpful in navigating a complete binary tree. Imagine a complete binary tree with 10 levels. Until the 9th level, every single node has both a left child and a right child, so you have no way of knowing which path to take down to the leaves. So the question is, what information do you add to each node? I would add the count of nodes in that tree.
Maintaining the count is easy, since every time you descend down a subtree you know to add one to the count at that node. What you want to recognize is the leftmost imperfect subtree. Every perfect binary tree has n = 2^k - 1, where k is the number of levels and n is the number of nodes. There are quick and easy ways to check if a number is 1 less than a power of two (see the first answer to this question), and in fact in a complete binary tree every node has at most one child that isn't the root of a perfect binary tree. Follow a simple rule to add nodes:
If the left child is None, set root.left = Node(key) and return
Else if the right child is None, set root.right = Node(key) and return
If one of the children of the current node is the root of an imperfect subtree, make that node the current node (descend down that subtree)
Else if the sizes are unequal, make the node with the smaller subtree the current node.
Else, make the left child the current node.
By augmenting each node with the size of the subtree rooted there, you have all the information you need at every node to build a recursive solution.

Linked Lists Homework in Python

I have to do the following for my homework:
Suppose that your linked list is not empty, and is pointed to by the variable head. Write a method called findMin() that goes through the list and finds the least value (you can compare strings just like numbers) and returns that.
Suppose that your linked list is not empty, and is pointed to by the variable head. Write a method called getLast() that goes through the list and returns the last thing in the list. This is the value of the last node in the linked list chain.
Finish the append method. It runs to the end and attaches the new node to the last existing nodes in the chain. If there are no nodes in the chain yet, it sets self.head to that node. Beware! This is tricky! You might consult your textbook.
I attempted to solve the first one and I am just lost, I don't know if I am messing up the class structure or what (I just learned classes like 3 days ago)
Here's my attempt...
class LinkedList:
class Node:
def __init__(self,value):
self.value = value
self.next = none
def __init__(self):
self.head = None
def findMin(self):
currentNode = self.head
minNode = self.head
while currentNode != none:
if currentNode.next < currentNode:
minNode = currentNode
currentNode = currentNode.next
return minNode
As #mgilson mentioned in comments, there are a number of things wrong, and not all were listed.
I think you would benefit a lot from writing (in comments, why not) what you think each line is doing.
Let's start with
currentNode = self.head
I think this is trying to say "let's start at the head of the list, by setting currentNode to point to that".
As written, this appears to be accessing a member variable of the current node, called 'head'. But the Node class definition doesn't have a defined member called head! And... why do you need to do that? You are told "the current node is the head"!
So you probably mean
currentNode = self # start at the head of the list
Next:
minNode = self.head
This is saying "The node with the current known minimum is stored in this one"
As before, you probably mean:
minNode = self # The head currently has the minimum we know about
Then:
while currentNode != none:
First, if you use a syntax highlighting editor, straight away it will tell you that 'None' has a capital 'N'.
No other problems here.
Then:
if currentNode.next < currentNode:
minNode = currentNode
currentNode = currentNode.next
This is trying to say "if the value of the next node is less than the value of this one, then the minimum is now ..." ... actually what? It's saying it's the current one! But it's not: if this if statement is true, then the next one contains the minimum! And hang on, shouldn't we be comparing with minNode, not currentNode?
Seems like you mean
if currentNode.value < minNode.value:
minNode = currentNode # this node is now the smallest we know about
and this next line needs to be outside the if loop, because it moves us on to the next node:
currentNode = currentNode.next # move on to the next node
Phew, nearly there: now we have to return the minimum value not, the node that has the minimum value (read the instructions carefullly.
return minNode.value
HTH
Ok, let's do this homework!
Let Node be a list cell. It has two fields value and next (also called car/cdr or head/tail in functional languages).
class Node:
def __init__(self, value):
self.value = value
self.next = None
append(node, other) can be defined as follows: if node has no "next" node (i.e. it is the last), attach other right to this node. Otherwise, take the "next" node, and append (recursively) other to that one:
def append(self, other):
if self.next:
self.next.append(other)
else:
self.next = other
Now let's define two essential functionals: map and reduce. map applies a function to each node in order and returns the list of results:
def map(self, fn):
r = [fn(self.value)]
if self.next:
r += self.next.map(fn)
return r
reduce applies a function to each node and combines results into a single value:
def reduce(self, fn, r=None):
if r is None:
r = self.value
else:
r = fn(r, self.value)
if self.next:
return self.next.reduce(fn, r)
return r
Now you're ready for the homework.
Create a list:
lst = Node(1)
lst.append(Node(8))
lst.append(Node(3))
lst.append(Node(7))
print all values:
print lst.map(lambda x: x)
find the last value:
print lst.reduce(lambda r, x: x)
find the max value:
print lst.reduce(max)
etc
Let us know if you have questions.

Recursion and return statements [duplicate]

This question already has answers here:
Why does my recursive function return None?
(4 answers)
Closed 8 months ago.
I'm fairly new to Python and recursive functions as a whole, so pardon my ignorance.
I am trying to implement a binary search tree in Python and have the following insert method (taken out of a class):
def insert(self, key, root=None):
'''Inserts a node in the tree'''
if root == None:
root = self.root
if root.key == None:
self._update(root, key)
return 0
else:
tmp = root
if key > tmp.key: # we work with the right subtree
self.insert(key, root=tmp.right)
elif key < tmp.key: # we work with the left subtree
self.insert(key, root=tmp.left)
else: # key already exists
return 0
I'm not sure if this is legible, but it traverses the tree until it gets to a None value and updates the node with the key to insert.
Now, the method works nicely and correctly creates a BST from scratch. But there's a problem with the return statements, as it only returns 0 if there is no recursion performed.
>>> bst.insert(10)
0
>>> bst.insert(15)
>>> bst.root.right.key
15
>>>
"Inserting" the root key again returns 0 (from line 15) the way it should.
>>> bst.insert(10)
0
I can't figure out why this happens. If I put a print statement in line 6, it executes correctly, yet it just won't return anything past the first insertion. Why is this? (I'm pretty sure I'm missing some basic information regarding Python and recursion)
Thanks for your help,
Ivan
P.S.: I've read that recursion is not the best way to implement a BST, so I'll look into other solutions, but I'd like to know the answer to this before moving on.
On your recursive lines, you do not return anything. If you want it to return 0, you should replace them with lines like:
return self.insert(key, root=tmp.left)
instead of just
self.insert(key, root=tmp.left)
You are inside a function and want to return a value, what do you do? You write
def function():
return value
In your case you want to return the value returned by a function call, so you have to do.
def function():
return another_function()
However you do
def function():
another_function()
Why do you think that should work? Of course you use recursion but in such a case you should remember the Zen of Python which simply says:
Special cases aren't special enough to break the rules.
You need a return statement in your recursive case. Try this adjustment.
def insert(self, key, root=None):
'''Inserts a node in the tree'''
if root == None:
root = self.root
if root.key == None:
self._update(root, key)
return 0
else:
tmp = root
if key > tmp.key: # we work with the right subtree
return self.insert(key, root=tmp.right)
elif key < tmp.key: # we work with the left subtree
return self.insert(key, root=tmp.left)
else: # key already exists
return 0

Categories

Resources