Sum of binary tree leaves' values - python

I wrote this code and when I use print I see that I get the leaves. However, the final return from the function is None and not the sum of the leaves, which is supposed to be 7 in this example. I'd be happy to know whats wrong here. Thank you !
class Node:
def __init__(self, val=None):
self.left = None
self.right = None
self.val = val
def sum_leafs(tree):
if tree is None:
return 0
if tree.right and tree.left:
sum_leafs(tree.right)
sum_leafs(tree.left)
elif tree.right or tree.left:
if tree.right:
sum_leafs(tree.right)
elif tree.left:
sum_leafs(tree.left)
elif tree.right is None and tree.left is None:
return sum_leafs(tree.left) + 1
node = Node(10)
node.right = Node(2)
node.left = Node(11)
node.left.right = Node(5)
print(sum_leafs(node))

You forgot to add + when you sum the branches (left/right) and also you forgot to access val which is the most crucial thing for the whole thing to work.
Further, the logic can be simplified:
def sum_leafs(tree):
if tree is None:
return 0
if not tree.right and not tree.left:
return tree.val
return sum_leafs(tree.right) + sum_leafs(tree.left)

You are not adding the sums together or returning them. This can also be done with a method in the class:
class Node:
def __init__(self, val=None):
self.left = None
self.right = None
self.val = val
def sum(self):
s = 0
if self.left is not None:
s += self.left.sum()
if self.right is not None:
s += self.right.sum()
return self.val + s
node = Node(10)
node.right = Node(2)
node.left = Node(11)
node.left.right = Node(5)
print(node.sum())
returns:
28

You are not properly returning the calculated leaf sums. Try this:
class Node:
def __init__(self, val=None):
self.left = None
self.right = None
self.val = val
def sum_leafs(tree):
if tree is None:
return 0
elif tree.right and tree.left:
return sum_leafs(tree.right) + sum_leafs(tree.left)
elif tree.right or tree.left:
if tree.right:
return sum_leafs(tree.right)
elif tree.left:
return sum_leafs(tree.left)
elif tree.right is None and tree.left is None:
return tree.val
node = Node(10)
node.right = Node(2)
node.left = Node(11)
node.left.right = Node(5)
print(sum_leafs(node))
7

node
First I'm going to update your Node interface so that it's possible to set left and right branches when creating nodes -
class Node:
def __init__(self, val=None, left=None, right=None):
self.left = left
self.right = right
self.val = val
This allows us to create tress more ergonomically, such as -
t = Node(10, Node(11, None, Node(5)), Node(2))
traverse
Now we write a generic traverse procedure. This allows us to separate 1) the traversal of our tree from 2) the intended operation we want to perform on each tree element -
def traverse(tree):
if tree is None:
return
else:
yield tree.val
yield from traverse(tree.left)
yield from traverse(tree.right)
Now the need for sum_leafs disappears. We have decoupled traversal logic from summing logic. We can calculate the sum of leafs with a simple combination of sum and traverse -
print(sum(traverse(t)))
# 28
don't repeat yourself
Or, instead of summing the values, we could write a search function to find the first value that passes a predicate -
def search(test, tree):
for val in traverse(tree):
if test(val):
return val
print(search(lambda x: x < 10, t))
# 5
print(search(lambda x: x > 99, t))
# None
Or, we could simply collect each value into a list -
print(list(traverse(t)))
# [ 10, 11, 5, 2 ]
As you can see, removing the traversal logic from each function that depends on our tree can be a huge help.
without generators
If you don't like generators, you can write the eager version of traverse which always returns a list. The difference now is there is no way to partially traverse the tree. Note the similarities this program shares with the generator version -
def traverse(t):
if t is None:
return [] # <-- empty
else:
return \
[ t.val
, *traverse(t.left) # <-- yield from
, *traverse(t.right) # <-- yield from
]
print(traverse(t))
# [ 10, 11, 5, 2 ]

Related

Trouble balancing a binary search tree using AVL

I'm unable to identify where I'm going wrong with my AVL implementation for balancing an existing binary search tree. I'm not getting any errors but my binary search tree does not come out to be properly balanced. After insertion, my binary search tree looks like (it would be prudent here to mention that my display_keys method gives us a visualization that is rotated by 90 degrees):
∅
The-Dreamers
∅
Saint-Laurent
∅
Pierrot-le-Fou
∅
Contempt
Cold-War
Before-Sunrise
∅
Basic-Instinct
∅
This is correct as it seems be to be following the rules for a BST.
But after calling the BalanceTree() method on my binary search tree, I seem to get:
∅
The-Dreamers
Saint-Laurent
Pierrot-le-Fou
Contempt
Cold-War
Before-Sunrise
Basic-Instinct
∅
which as you can see, is not Balanced. But wait, and here's the catch, if I call BalanceTree() again, the tree comes out to be perfectly balanced and isBSTBalanced returns True also. After the second call to BalanceTree(), our tree looks like:
∅
The-Dreamers
Saint-Laurent
Pierrot-le-Fou
Contempt
Cold-War
Before-Sunrise
Basic-Instinct
∅
I am adding the complete source code for my BST class for clarity and if somebody wants to execute the code, but I have added a comment (#Addition of new methods for AVL starts here) in the BST class to indicate where the methods for AVL start. You need only concern yourself with them. I would like for you help me pinpoint what exactly is going wrong in my code.
class BST:
class TreeNode:
def __init__(self, key, value, left=None, right=None, parent=None):
self.key = key
self.value = value
self.left = left
self.right = right
self.parent = parent
self.height = 1
def __init__(self):
self.root = None
self.size = 0
def __len__(self):
return self.size
def insert(self, key, value):
if self.root == None:
self.root = self.TreeNode(key, value)
else:
self._insert(key, value, self.root)
self.size += 1
def _insert(self, key, value, curr_node):
if key < curr_node.key:
if curr_node.left is not None:
self._insert(key, value, curr_node.left)
else:
curr_node.left = self.TreeNode(key, value, parent=curr_node)
elif key > curr_node.key:
if curr_node.right is not None:
self._insert(key, value, curr_node.right)
else:
curr_node.right = self.TreeNode(key, value, parent=curr_node)
def search(self, key):
if self.root:
found = self._search(key, self.root)
if found:
return found.value
else:
return None
else:
return None
def _search(self, key, curr_node):
if not curr_node:
return None
elif curr_node.key == key:
return curr_node
elif key < curr_node.key:
return self._search(key, curr_node.left)
else:
return self._search(key, curr_node.right)
def find_min(self):
curr = self.root
while curr.left is not None:
curr = curr.left
return curr
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)
self.delete(minimum.key) #We will still have a ref to this node afterwards
node_to_remove.key, node_to_remove.value = minimum.key, minimum.value
#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
def traversal(self, root):
res = []
if root:
res = self.traversal(root.left)
res.append(root)
res = res + self.traversal(root.right)
return res
def inorder_traversal(self, root):
if root:
self.inorder_traversal(root.left)
print(root.key)
self.inorder_traversal(root.right)
#Addition of new methods for AVL starts here
def display_keys(self, node, space='\t', level=0):
"""
Allows us to visualize the tree (albiet rotated by 90 degrees)
"""
# print(node.key if node else None, level)
# If the node is empty
if node is None:
print(space*level + '∅')
return
# If the node is a leaf
if node.left is None and node.right is None:
print(space*level + str(node.key))
return
# If the node has children
self.display_keys(node.right, space, level+1)
print(space*level + str(node.key))
self.display_keys(node.left,space, level+1)
def height(self):
return self._height(self.root)
def _height(self, curr_node):
if curr_node is None:
return -1 #since we are counting number of edges, we will return -1
else:
return 1 + max(self._height(curr_node.left), self._height(curr_node.right))
def isBSTBalanced(self):
return self._isBSTBalanced(self.root)
def _isBSTBalanced(self, curr_node):
if curr_node is None:
return True
hleft_subtree = self._height(curr_node.left)
hright_subtree = self._height(curr_node.right)
if hleft_subtree - hright_subtree in [-1,0,1]:
return self._isBSTBalanced(curr_node.left) and self._isBSTBalanced(curr_node.right)
else:
return False
def balance_factor(self):
if self.root is not None:
return self._balance_factor(self.root)
else:
return 0
def _balance_factor(self, curr_node):
if curr_node is None:
return
hleft_subtree = self._height(curr_node.left)
hright_subtree = self._height(curr_node.right)
b_factor = hleft_subtree - hright_subtree
return b_factor
def BalanceTree(self):
if self.isBSTBalanced() == False:
return self._rebalance(self.root)
def _rebalance(self, curr_node):
if curr_node is None:
return None
curr_node.left = self._rebalance(curr_node.left)
curr_node.right = self._rebalance(curr_node.right)
curr_node.height = 1 + max(self._height(curr_node.left), self._height(curr_node.right))
#print(curr_node.height)
if self._balance_factor(curr_node) > 1 and self._balance_factor(curr_node.left) >= 0:
#left heavy subtree
return self._rotate_right(curr_node)
if self._balance_factor(curr_node) < -1 and self._balance_factor(curr_node.right) <= 0:
#right heavy subtree
return self._rotate_left(curr_node)
if self._balance_factor(curr_node) < 0 and self._balance_factor(curr_node.right) > 0:
self._rotate_right(curr_node.right)
return self._rotate_left(curr_node)
if self._balance_factor(curr_node) > 0 and self._balance_factor(curr_node.left) < 0:
self._rotate_left(curr_node.left)
return self._rotate_right(curr_node)
return curr_node
def _rotate_left(self, oldRoot):
newRoot = oldRoot.right #the newRoot is the right child of the previous root
oldRoot.right = newRoot.left #replacing right child of the old root with the left child of the new
if newRoot.left is not None:
newRoot.left.parent = oldRoot
newRoot.parent = oldRoot.parent
if oldRoot == self.root:
self.root = newRoot
else:
if oldRoot.parent.left is oldRoot: #Checking isLeftChild
oldRoot.parent.left = newRoot
else:
oldRoot.parent.right = newRoot
newRoot.left = oldRoot
oldRoot.parent = newRoot
oldRoot.height = 1 + max(self._height(oldRoot.left), self._height(oldRoot.right))
newRoot.height = 1 + max(self._height(newRoot.left), self._height(newRoot.right))
return newRoot
def _rotate_right(self, oldRoot):
newRoot = oldRoot.left #the newRoot is the left child of the previous root
oldRoot.left = newRoot.right #replacing left child of the old root with the right child of the new
if newRoot.right is not None:
newRoot.right.parent = oldRoot
newRoot.parent = oldRoot.parent
if oldRoot == self.root:
self.root = newRoot
else:
if oldRoot.parent.right is oldRoot: #Checking isRightChild
oldRoot.parent.right = newRoot
else:
oldRoot.parent.left = newRoot
newRoot.right = oldRoot
oldRoot.parent = newRoot
oldRoot.height = 1 + max(self._height(oldRoot.left), self._height(oldRoot.right))
newRoot.height = 1 + max(self._height(newRoot.left), self._height(newRoot.right))
return newRoot
if __name__ == '__main__':
obj = BST()
obj.insert('Basic-Instinct', 0)
obj.insert('The-Dreamers', 1)
obj.insert('Saint-Laurent', 2)
obj.insert('Pierrot-le-Fou', 3)
obj.insert('Contempt', 4)
obj.insert('Before-Sunrise', 5)
obj.insert('Cold-War', 8)
obj.display_keys(obj.root) #displays a visual representation of our tree, albeit rotated by 90 degrees
print()
print("isBSTBalanced:", obj.isBSTBalanced())
obj.BalanceTree()
print("isBSTBalanced:", obj.isBSTBalanced()) #After executing BalanceTree(), isBSTBalanced still returns False
print()
obj.display_keys(obj.root)
Progress: Revamped _isBSTBalanced method so that it visits every node recursively and not just the root node. The final outcome, however, remains the same.
Progress: I was able to identify one of the major issues being that while I was calling _rotate_left and _rotate_right methods in the _rebalance method, I was not returning them. In addition to this, I was not recursively visiting the left and right subtrees of curr_node, which was initially set to the root of the tree, to be able to traverse the tree in a bottom up manner. I have resolved this too. I have added a display_keys method which allows us to visualize the tree, albeit rotated by 90 degrees. I'm updating the code and prompt in this post accordingly. The problem that still remains is that I have to call the BalanceTree() method more than once in some cases for isBSTBalanced to return True.

how do I print the value of nodes in a binary tree, that have been added to a queue?

I am writing code to solve the following Leetcode solution:
https://leetcode.com/problems/symmetric-tree/
#THIS FIRST CHUNK OF CODE IS JUST TO BUILD A BINARY TREE!
from collections import deque
class TreeNode:
def __init__(self, val):
self.val = val
self.left = None
self.right = None
def insert(self, val):
# Compare the new value with the parent node
if self.val:
if val <= self.val:
if self.left is None:
self.left = TreeNode(val)
else:
self.left.insert(val)
elif val >= self.val:
if self.right is None:
self.right = TreeNode(val)
else:
self.right.insert(val)
else:
self.val = val
def PrintTree(self):
if self.left:
self.left.PrintTree()
print(self.val),
if self.right:
self.right.PrintTree()
#THIS IS THE CODE TO SOLVE THE LEETCODE PROBLEM
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
queue = deque()
if not root:
return []
if root.left:
queue.append(root.left)
if root.right:
queue.append(root.right)
right_subt = []
left_subt = []
while queue:
level_length = len(queue)
for _ in range(level_length // 2):
node = queue.popleft()
left_subt.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
for _ in range((level_length - (level_length // 2))):
node = queue.popleft()
right_subt.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
print(queue)
if left_subt != right_subt.reverse():
return False
return True
root = TreeNode(1)
root.insert(2)
root.insert(2)
root.insert(3)
root.insert(4)
root.insert(4)
root.insert(3)
root.PrintTree()
x=Solution()
Solution.isSymmetric(x,root)
My code fails the first input: root = [1,2,2,3,4,4,3] ; it should return True but it is retuning False, and I am trying to debug it.
In my code above I build a tree using class TreeNode, and I try to print the queue, however what I get is: deque([<__main__.TreeNode object at 0x7fe9381dc340>, <__main__.TreeNode object at 0x7fe9381dc820>])
Any ideas how I can print the queue to show the node values?
Most elegant would be to define the __repr__ function for the TreeNode class, e.g.
def __repr__(self):
return f"Node {self.val}"
This determines how the TreeNode class is printed. In this case you get
deque([Node 2, Node 3])
Then you can also adapt it according to your needs, e.g. if you want to print left and right in addition to the value.

adding a node in Binary Search Tree

I am currently working on implementing a Binary search tree but I have a problem with creating the add_node() function. I tried to make it in a recursive way, but the result was weird.
For example- If I run add_node(1), add_node(5), and add_node(0), my tree has only 1, no 5 and 0.
I would be happy if you tell me what the problem is.
def add_node(self, value: int) -> None:
if self.root == None:
self.root = Node(value)
return
else:
return self.add_recursion(self.root, value)
def add_recursion(self, node: Node, value: int) -> None:
if node == None:
node = Node(value)
return
elif value < node.value:
return self.add_recursion(node.left, value)
else:
return self.add_recursion(node.right, value)
When a None value is passed into a function, it is passed by value, not by reference, since... there is no reference.
elif value < node.value:
return self.add_recursion(node.left, value)
else:
return self.add_recursion(node.right, value)
When node.left or node.right is None, a Node ends up being created but not attached to node.
So what you could do is handle the cases where they are None separately.
def add_recursion(self, node: Node, value: int) -> None:
if node == None:
node = Node(value)
elif value < node.value:
if node.left == None:
node.left = Node(value)
else:
self.add_recursion(node.left, value)
else:
if node.right == None:
node.right = Node(value)
else:
self.add_recursion(node.right, value)
While this is workable, it becomes quite ugly. Look at the answer by Locke to see a better way to structure your code.
Also, an additional tip for readability, avoid using return where they aren't necessary such as at the end of a flow.
The issue is how you handle if node.left or node.right does not exist. Since the arguments are copied for each call, setting the value of node in add_recursion has no effect on the tree.
def foo(val):
print("foo start:", val)
val = 5
print("foo end:", val)
bar = 3
foo(bar) # Value of bar is copied for call
print("after foo:", bar) # Prints bar is still 3
I think you might also be getting confused due to how the root node is handled. Here is some starter code on how can handle the initial call to add_recursive.
class Node:
def __init__(self, value: int):
self.value = value
self.left = None
self.right = None
def add_recursive(self, value: int):
# TODO: recursively add to left or right
# For example, here is how you could recursively make a linked list
if self.right is None:
self.right = Node(value)
else:
self.right.add_recursive(value)
class BinarySearchTree:
def __init__(self):
self.root = None
def add_node(self, value: int):
if self.root is None:
self.root = Node(value)
else:
# Call add_recursive on root instead of with root.
self.root.add_recursive(value)
tree = BinarySearchTree()
tree.add_node(1)
tree.add_node(5)
tree.add_node(0)

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))

Python Serializing/Deserializing a binary tree

I'm trying to implement a serializing/deserializing algorithm in python for binary trees.
Here's my code:
class Node:
count = 1
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def insert(self, value):
if self.value > value:
if self.left is None:
self.left = Node(value)
Node.count += 1
else:
self.left.insert(value)
else:
if self.right is None:
self.right = Node(value)
Node.count += 1
else:
self.right.insert(value)
# Using preorder
def serialize(root, serial):
if root != None:
serial.append(root.value)
serialize(root.left, serial)
serialize(root.right, serial)
else:
serial.append('x')
def deserialize(newRoot, serial):
if serial[0] == 'x':
serial.pop(0)
else:
if len(serial) > 0:
newRoot = Node(serial.pop(0))
print(newRoot.value)
deserialize(newRoot.left, serial)
deserialize(newRoot.right, serial)
print("This program serializes a tree\n")
root = Node(3)
root.insert(1)
root.insert(2)
root.insert(4)
root.insert(5)
root.insert(0)
# Serialize
serial = []
serialize(root, serial)
print(serial)
# Deserialize
newRoot = Node(None)
deserialize(newRoot, serial)
print(newRoot.value)
The problem is, newRoot doesn't get updated by deserialize because python passes it by value. How do I get around this, preferably in the most elegant way? In C/C++, I would just pass a pointer to newRoot and it should get updated accordingly. Thanks!
You can return the newly created nodes and assign them as left and right nodes. Also poping the first element of a list is more costly than poping the last element, so reverseing the list at the beginning and then using it in the recursion will be more performant in your case. So the code will become something like:
def deserialize(serial):
serial.reverse()
return _deserialize(serial)
def _deserialize(serial):
if not serial:
return None
node = None
value = serial.pop()
if value != 'x':
node = Node(value)
node.left = _deserialize(serial)
node.right = _deserialize(serial)
return node
root = deserialize(serial)
print(root.value)
You can create left and right subtree within deserialize function and return the root.
Here is my code:
node_list = []
MARKER = -1
class Node:
def __init__(self, val):
self.val = val
self.left = None
self.right = None
def serialize(root):
if root is None:
node_list.append(MARKER)
return
node_list.append(root.val)
serialize(root.left)
serialize(root.right)
def deserialize(root, node_list):
if node_list:
val = node_list.pop(0)
else:
return
if val == MARKER:
return
# Create root, left and right recursively
root = Node(val)
root.left = deserialize(root.left, node_list)
root.right = deserialize(root.right, node_list)
return root
def inorder_traversal(root):
if root:
inorder_traversal(root.left)
print(root.val, end=' ')
inorder_traversal(root.right)
if __name__=="__main__":
# Create tree
root = Node(20)
root.left = Node(8)
root.right = Node(22)
root.left.left = Node(4)
root.left.right = Node(12)
root.left.right.left = Node(10)
root.left.right.right = Node(14)
print("Inorder traversal before serialization..")
inorder_traversal(root)
print('')
# serialize the tree and insert elements into a list
serialize(root)
print(node_list)
root1 = None
root1 = deserialize(root1, node_list)
print("Inorder traversal after deserialization..")
inorder_traversal(root1)
print('')

Categories