A Binary Tree is a non-linear data structure that is used for searching and data organization. A binary tree is comprised of nodes. Each node being a data component, one a left child and the other the
A Binary Tree is a non-linear data structure that is used for searching and data organization. A binary tree is comprised of nodes. Each node being a data component, one a left child and the other the right child. Let us dive into the concepts related to trees and implement them into the Python programming language.
Note: Prerequisites – Make sure you have basic Python knowledge before diving into this article. It also might be a good idea to check out some linear data structures. (links are given above)
A binary tree node consists of the following components:
Data
Pointer to Left Child
Pointer to Right Child
Below are some key terminologies related to a binary tree.
Node – The most elementary unit of a binary tree.
Root – The root of a binary is the topmost element. There is only one root in a binary tree.
Leaf – The leaves of a binary tree are the nodes which have no children.
Level – The level is the generation of the respective node. The root has level 0, the children of the root node is at level 1, the grandchildren of the root node is at level 2 and so on.
Parent – The parent of a node is the node that is one level upward of the node.
Child – The children of a node are the nodes that are one level downward of the node.
Applications of Binary Trees
A binary tree is a hierarchical data structure, a file system that is organized in the form of a tree. Trees can be used for efficient searching, when the elements are organized with some order. Some examples include:
Abstract Syntax Trees and Parse Trees are constructed by a compiler as a part of compilation.
Trees are also used to efficiently index databases.
Implementing a Binary Tree
Initialize a Node Class
Let us first define the Node class.
# The Node Class defines the structure of a Node
class Node:
# Initialize the attributes of Node
def __init__(self, data):
self.left = None # Left Child
self.right = None # Right Child
self.data = data # Node Data
Once we have defined the Node class, we can initialize our Binary Tree:
class Node:
def __init__(self, data):
self.left = None
self.right = None
self.data = data
root = Node(10) # Instantiating the Tree
# Tree Structure
# 10
# / \
# None None
root.left = Node(34) # Setting the left child of the root to 34
root.right = Node(89) # Setting the right child of the root to 89
# Tree Structure
# 10
# / \
# 34 89
# / \ / \
# None None None None
Traversals
Since a binary tree is a non-linear data structure, there is more than one way to traverse through the tree data. Let’s look at the various types of traversals in a binary tree, including inorder traversal, preorder traversal, and postorder traversal.
Inorder Traversal
In an inorder traversal, the left child is visited first, followed by the parent node, then followed by the right child.
def inorder(node):
if node:
# Recursively call inorder on the left subtree until it reaches a leaf node
inorder(node.left)
# Once we reach a leaf, we print the data
print(node.data)
# Now, since the left subtree and the root has been printed, call inorder on right subtree recursively until we reach a leaf node.
inorder(node.right)
# For the tree,
# 10
# / \
# 34 89
# / \ / \
# 20 45 56 54
# Inorder traversal: 20 34 45 10 56 89 54
Preorder Traversal
In a preorder traversal, the root node is visited first, followed by the left child, then the right child.
def preorder(node):
if node:
# Print the value of the root node first
print(node.data)
# Recursively call preorder on the left subtree until we reach a leaf node.
preorder(node.left)
# Recursively call preorder on the right subtree until we reach a leaf node.
preorder(node.right)
# For the tree,
# 10
# / \
# 34 89
# / \ / \
# 20 45 56 54
# Preorder traversal: 10 34 20 45 89 56 54
Postorder Traversal
In a postorder traversal, the left child is visited first, followed by the right child, then the root node.
def postorder(node):
if node:
# Recursively call postorder on the left subtree until we reach a leaf node.
postorder(node.left)
# Recursively call postorder on the right subtree until we reach a leaf node.
postorder(node.right)
# Print the value of the root node
print(node.data)
# For the tree,
# 10
# / \
# 34 89
# / \ / \
# 20 45 56 54
# Postorder traversal: 20 45 34 56 54 89 10
Practice Binary Trees
Once you have understood the core concepts of a binary tree, practice the problem sets given below to strengthen and test your knowledge on trees.
from__future__import annotationsclassNode:""" A Node has data variable and pointers to Nodes to its left and right. """def__init__(self,data:int) ->None: self.data = data self.left: Node |None=None self.right: Node |None=Nonedefdisplay(tree: Node |None) ->None: # In Order traversal of the tree""">>> root = Node(1)>>> root.left = Node(0)>>> root.right = Node(2)>>> display(root) 0 1 2>>> display(root.right) 2 """if tree:display(tree.left)print(tree.data)display(tree.right)defdepth_of_tree(tree: Node |None) ->int:""" Recursive function that returns the depth of a binary tree.>>> root = Node(0)>>> depth_of_tree(root) 1>>> root.left = Node(0)>>> depth_of_tree(root) 2>>> root.right = Node(0)>>> depth_of_tree(root) 2>>> root.left.right = Node(0)>>> depth_of_tree(root) 3>>> depth_of_tree(root.left) 2 """return1+max(depth_of_tree(tree.left), depth_of_tree(tree.right))if tree else0defis_full_binary_tree(tree: Node) ->bool:""" Returns True if this is a full binary tree>>> root = Node(0)>>> is_full_binary_tree(root) True>>> root.left = Node(0)>>> is_full_binary_tree(root) False>>> root.right = Node(0)>>> is_full_binary_tree(root) True>>> root.left.left = Node(0)>>> is_full_binary_tree(root) False>>> root.right.right = Node(0)>>> is_full_binary_tree(root) False """ifnot tree:returnTrueif tree.left and tree.right:returnis_full_binary_tree(tree.left)andis_full_binary_tree(tree.right)else:returnnot tree.left andnot tree.rightdefmain() ->None: # Main function for testing. tree =Node(1) tree.left =Node(2) tree.right =Node(3) tree.left.left =Node(4) tree.left.right =Node(5) tree.left.right.left =Node(6) tree.right.left =Node(7) tree.right.left.left =Node(8) tree.right.left.left.right =Node(9)print(is_full_binary_tree(tree))print(depth_of_tree(tree))print("Tree is: ")display(tree)if__name__=="__main__":main()
# https://en.wikipedia.org/wiki/Tree_traversalfrom__future__import annotationsfrom dataclasses import dataclass@dataclassclassNode: data:int left: Node |None=None right: Node |None=Nonedefmake_tree() -> Node:returnNode(1, Node(2, Node(4), Node(5)), Node(3))defpreorder(root: Node):""" Pre-order traversal visits root node, left subtree, right subtree.>>> preorder(make_tree()) [1, 2, 4, 5, 3] """return [root.data] +preorder(root.left)+preorder(root.right)if root else []defpostorder(root: Node):""" Post-order traversal visits left subtree, right subtree, root node.>>> postorder(make_tree()) [4, 5, 2, 3, 1] """returnpostorder(root.left)+postorder(root.right)+ [root.data] if root else []definorder(root: Node):""" In-order traversal visits left subtree, root node, right subtree.>>> inorder(make_tree()) [4, 2, 5, 1, 3] """returninorder(root.left)+ [root.data] +inorder(root.right)if root else []defheight(root: Node):""" Recursive function for calculating the height of the binary tree.>>> height(None) 0>>> height(make_tree()) 3 """return (max(height(root.left), height(root.right))+1) if root else0deflevel_order_1(root: Node):""" Print whole binary tree in Level Order Traverse. Level Order traverse: Visit nodes of the tree level-by-level. """ifnot root:return temp = root que = [temp]whilelen(que)>0:print(que[0].data, end=" ") temp = que.pop(0)if temp.left: que.append(temp.left)if temp.right: que.append(temp.right)return quedeflevel_order_2(root: Node,level:int):""" Level-wise traversal: Print all nodes present at the given level of the binary tree """ifnot root:return rootif level ==1:print(root.data, end=" ")elif level >1:level_order_2(root.left, level -1)level_order_2(root.right, level -1)defprint_left_to_right(root: Node,level:int):""" Print elements on particular level from left to right direction of the binary tree. """ifnot root:returnif level ==1:print(root.data, end=" ")elif level >1:print_left_to_right(root.left, level -1)print_left_to_right(root.right, level -1)defprint_right_to_left(root: Node,level:int):""" Print elements on particular level from right to left direction of the binary tree. """ifnot root:returnif level ==1:print(root.data, end=" ")elif level >1:print_right_to_left(root.right, level -1)print_right_to_left(root.left, level -1)defzigzag(root: Node):""" ZigZag traverse: Print node left to right and right to left, alternatively. """ flag =0 height_tree =height(root)for h inrange(1, height_tree +1):if flag ==0:print_left_to_right(root, h) flag =1else:print_right_to_left(root, h) flag =0defmain(): # Main function for testing.""" Create binary tree. """ root =make_tree()""" All Traversals of the binary are as follows: """print(f" In-order Traversal is {inorder(root)}")print(f" Pre-order Traversal is {preorder(root)}")print(f"Post-order Traversal is {postorder(root)}")print(f"Height of Tree is {height(root)}")print("Complete Level Order Traversal is : ")level_order_1(root)print("\nLevel-wise order Traversal is : ")for h inrange(1, height(root) +1):level_order_2(root, h)print("\nZigZag order Traversal is : ")zigzag(root)print()if__name__=="__main__":import doctest doctest.testmod()main()
"""A binary search Tree"""classNode:def__init__(self,value,parent): self.value = value self.parent = parent # Added in order to delete a node easier self.left =None self.right =Nonedef__repr__(self):from pprint import pformatif self.left isNoneand self.right isNone:returnstr(self.value)returnpformat({"%s"% (self.value): (self.left, self.right)}, indent=1)classBinarySearchTree:def__init__(self,root=None): self.root = rootdef__str__(self):""" Return a string of all the Nodes using in order traversal """returnstr(self.root)def__reassign_nodes(self,node,new_children):if new_children isnotNone:# reset its kids new_children.parent = node.parentif node.parent isnotNone:# reset its parentif self.is_right(node):# If it is the right children node.parent.right = new_childrenelse: node.parent.left = new_childrenelse: self.root = new_childrendefis_right(self,node):return node == node.parent.rightdefempty(self):return self.root isNonedef__insert(self,value):""" Insert a new node in Binary Search Tree with value label """ new_node =Node(value, None)# create a new Nodeif self.empty():# if Tree is empty self.root = new_node # set its rootelse:# Tree is not empty parent_node = self.root # from rootwhileTrue:# While we don't get to a leafif value < parent_node.value:# We go leftif parent_node.left isNone: parent_node.left = new_node # We insert the new node in a leafbreakelse: parent_node = parent_node.leftelse:if parent_node.right isNone: parent_node.right = new_nodebreakelse: parent_node = parent_node.right new_node.parent = parent_nodedefinsert(self,*values):for value in values: self.__insert(value)return selfdefsearch(self,value):if self.empty():raiseIndexError("Warning: Tree is empty! please use another.")else: node = self.root# use lazy evaluation here to avoid NoneType Attribute errorwhile node isnotNoneand node.value isnot value: node = node.left if value < node.value else node.rightreturn nodedefget_max(self,node=None):""" We go deep on the right branch """if node isNone: node = self.rootifnot self.empty():while node.right isnotNone: node = node.rightreturn nodedefget_min(self,node=None):""" We go deep on the left branch """if node isNone: node = self.rootifnot self.empty(): node = self.rootwhile node.left isnotNone: node = node.leftreturn nodedefremove(self,value): node = self.search(value)# Look for the node with that labelif node isnotNone:if node.left isNoneand node.right isNone:# If it has no children self.__reassign_nodes(node, None)elif node.left isNone:# Has only right children self.__reassign_nodes(node, node.right)elif node.right isNone:# Has only left children self.__reassign_nodes(node, node.left)else: tmp_node = self.get_max( node.left )# Gets the max value of the left branch self.remove(tmp_node.value) node.value = ( tmp_node.value ) # Assigns the value to the node to delete and keep tree structuredefpreorder_traverse(self,node):if node isnotNone:yield node # Preorder Traversalyield from self.preorder_traverse(node.left)yield from self.preorder_traverse(node.right)deftraversal_tree(self,traversal_function=None):""" This function traversal the tree. You can pass a function to traversal the tree as needed by client code """if traversal_function isNone:return self.preorder_traverse(self.root)else:returntraversal_function(self.root)definorder(self,arr:list,node: Node):"""Perform an inorder traversal and append values of the nodes to a list named arr"""if node: self.inorder(arr, node.left) arr.append(node.value) self.inorder(arr, node.right)deffind_kth_smallest(self,k:int,node: Node) ->int:"""Return the kth smallest element in a binary search tree""" arr = [] self.inorder(arr, node)# append all values to list using inorder traversalreturn arr[k -1]defpostorder(curr_node):""" postOrder (left, right, self) """ node_list =list()if curr_node isnotNone: node_list =postorder(curr_node.left)+postorder(curr_node.right)+ [curr_node]return node_listdefbinary_search_tree():r""" Example 8 / \ 3 10 / \ \ 1 6 14 / \ / 4 7 13>>> t = BinarySearchTree().insert(8, 3, 6, 1, 10, 14, 13, 4, 7)>>> print(" ".join(repr(i.value) for i in t.traversal_tree())) 8 3 1 6 4 7 10 14 13>>> print(" ".join(repr(i.value) for i in t.traversal_tree(postorder))) 1 4 7 6 3 13 14 10 8>>> BinarySearchTree().search(6) Traceback (most recent call last): ... IndexError: Warning: Tree is empty! please use another. """ testlist = (8,3,6,1,10,14,13,4,7) t =BinarySearchTree()for i in testlist: t.insert(i)# Prints all the elements of the list in order traversalprint(t)if t.search(6)isnotNone:print("The value 6 exists")else:print("The value 6 doesn't exist")if t.search(-1)isnotNone:print("The value -1 exists")else:print("The value -1 doesn't exist")ifnot t.empty():print("Max Value: ", t.get_max().value)print("Min Value: ", t.get_min().value)for i in testlist: t.remove(i)print(t)if__name__=="__main__":import doctest doctest.testmod()# binary_search_tree()
"""This is a python3 implementation of binary search tree using recursionTo run tests:python -m unittest binary_search_tree_recursive.pyTo run an example:python binary_search_tree_recursive.py"""from__future__import annotationsimport unittestfrom typing import IteratorclassNode:def__init__(self,label:int,parent: Node |None) ->None: self.label = label self.parent = parent self.left: Node |None=None self.right: Node |None=NoneclassBinarySearchTree:def__init__(self) ->None: self.root: Node |None=Nonedefempty(self) ->None:""" Empties the tree>>> t = BinarySearchTree()>>> assert t.root is None>>> t.put(8)>>> assert t.root is not None """ self.root =Nonedefis_empty(self) ->bool:""" Checks if the tree is empty>>> t = BinarySearchTree()>>> t.is_empty() True>>> t.put(8)>>> t.is_empty() False """return self.root isNonedefput(self,label:int) ->None:""" Put a new node in the tree>>> t = BinarySearchTree()>>> t.put(8)>>> assert t.root.parent is None>>> assert t.root.label == 8>>> t.put(10)>>> assert t.root.right.parent == t.root>>> assert t.root.right.label == 10>>> t.put(3)>>> assert t.root.left.parent == t.root>>> assert t.root.left.label == 3 """ self.root = self._put(self.root, label)def_put(self,node: Node |None,label:int,parent: Node |None=None) -> Node:if node isNone: node =Node(label, parent)else:if label < node.label: node.left = self._put(node.left, label, node)elif label > node.label: node.right = self._put(node.right, label, node)else:raiseException(f"Node with label {label} already exists")return nodedefsearch(self,label:int) -> Node:""" Searches a node in the tree>>> t = BinarySearchTree()>>> t.put(8)>>> t.put(10)>>> node = t.search(8)>>> assert node.label == 8>>> node = t.search(3) Traceback (most recent call last): ... Exception: Node with label 3 does not exist """return self._search(self.root, label)def_search(self,node: Node |None,label:int) -> Node:if node isNone:raiseException(f"Node with label {label} does not exist")else:if label < node.label: node = self._search(node.left, label)elif label > node.label: node = self._search(node.right, label)return nodedefremove(self,label:int) ->None:""" Removes a node in the tree>>> t = BinarySearchTree()>>> t.put(8)>>> t.put(10)>>> t.remove(8)>>> assert t.root.label == 10>>> t.remove(3) Traceback (most recent call last): ... Exception: Node with label 3 does not exist """ node = self.search(label)if node.right and node.left: lowest_node = self._get_lowest_node(node.right) lowest_node.left = node.left lowest_node.right = node.right node.left.parent = lowest_nodeif node.right: node.right.parent = lowest_node self._reassign_nodes(node, lowest_node)elifnot node.right and node.left: self._reassign_nodes(node, node.left)elif node.right andnot node.left: self._reassign_nodes(node, node.right)else: self._reassign_nodes(node, None)def_reassign_nodes(self,node: Node,new_children: Node |None) ->None:if new_children: new_children.parent = node.parentif node.parent:if node.parent.right == node: node.parent.right = new_childrenelse: node.parent.left = new_childrenelse: self.root = new_childrendef_get_lowest_node(self,node: Node) -> Node:if node.left: lowest_node = self._get_lowest_node(node.left)else: lowest_node = node self._reassign_nodes(node, node.right)return lowest_nodedefexists(self,label:int) ->bool:""" Checks if a node exists in the tree>>> t = BinarySearchTree()>>> t.put(8)>>> t.put(10)>>> t.exists(8) True>>> t.exists(3) False """try: self.search(label)returnTrueexceptException:returnFalsedefget_max_label(self) ->int:""" Gets the max label inserted in the tree>>> t = BinarySearchTree()>>> t.get_max_label() Traceback (most recent call last): ... Exception: Binary search tree is empty>>> t.put(8)>>> t.put(10)>>> t.get_max_label() 10 """if self.root isNone:raiseException("Binary search tree is empty") node = self.rootwhile node.right isnotNone: node = node.rightreturn node.labeldefget_min_label(self) ->int:""" Gets the min label inserted in the tree>>> t = BinarySearchTree()>>> t.get_min_label() Traceback (most recent call last): ... Exception: Binary search tree is empty>>> t.put(8)>>> t.put(10)>>> t.get_min_label() 8 """if self.root isNone:raiseException("Binary search tree is empty") node = self.rootwhile node.left isnotNone: node = node.leftreturn node.labeldefinorder_traversal(self) -> Iterator[Node]:""" Return the inorder traversal of the tree>>> t = BinarySearchTree()>>> [i.label for i in t.inorder_traversal()] []>>> t.put(8)>>> t.put(10)>>> t.put(9)>>> [i.label for i in t.inorder_traversal()] [8, 9, 10] """return self._inorder_traversal(self.root)def_inorder_traversal(self,node: Node |None) -> Iterator[Node]:if node isnotNone:yield from self._inorder_traversal(node.left)yield nodeyield from self._inorder_traversal(node.right)defpreorder_traversal(self) -> Iterator[Node]:""" Return the preorder traversal of the tree>>> t = BinarySearchTree()>>> [i.label for i in t.preorder_traversal()] []>>> t.put(8)>>> t.put(10)>>> t.put(9)>>> [i.label for i in t.preorder_traversal()] [8, 10, 9] """return self._preorder_traversal(self.root)def_preorder_traversal(self,node: Node |None) -> Iterator[Node]:if node isnotNone:yield nodeyield from self._preorder_traversal(node.left)yield from self._preorder_traversal(node.right)classBinarySearchTreeTest(unittest.TestCase):@staticmethoddef_get_binary_search_tree() -> BinarySearchTree:r""" 8 / \ 3 10 / \ \ 1 6 14 / \ / 4 7 13 \ 5 """ t =BinarySearchTree() t.put(8) t.put(3) t.put(6) t.put(1) t.put(10) t.put(14) t.put(13) t.put(4) t.put(7) t.put(5)return tdeftest_put(self) ->None: t =BinarySearchTree()assert t.is_empty() t.put(8)r""" 8 """assert t.root isnotNoneassert t.root.parent isNoneassert t.root.label ==8 t.put(10)r""" 8 \ 10 """assert t.root.right isnotNoneassert t.root.right.parent == t.rootassert t.root.right.label ==10 t.put(3)r""" 8 / \ 3 10 """assert t.root.left isnotNoneassert t.root.left.parent == t.rootassert t.root.left.label ==3 t.put(6)r""" 8 / \ 3 10 \ 6 """assert t.root.left.right isnotNoneassert t.root.left.right.parent == t.root.leftassert t.root.left.right.label ==6 t.put(1)r""" 8 / \ 3 10 / \ 1 6 """assert t.root.left.left isnotNoneassert t.root.left.left.parent == t.root.leftassert t.root.left.left.label ==1with self.assertRaises(Exception): t.put(1)deftest_search(self) ->None: t = self._get_binary_search_tree() node = t.search(6)assert node.label ==6 node = t.search(13)assert node.label ==13with self.assertRaises(Exception): t.search(2)deftest_remove(self) ->None: t = self._get_binary_search_tree() t.remove(13)r""" 8 / \ 3 10 / \ \ 1 6 14 / \ 4 7 \ 5 """assert t.root isnotNoneassert t.root.right isnotNoneassert t.root.right.right isnotNoneassert t.root.right.right.right isNoneassert t.root.right.right.left isNone t.remove(7)r""" 8 / \ 3 10 / \ \ 1 6 14 / 4 \ 5 """assert t.root.left isnotNoneassert t.root.left.right isnotNoneassert t.root.left.right.left isnotNoneassert t.root.left.right.right isNoneassert t.root.left.right.left.label ==4 t.remove(6)r""" 8 / \ 3 10 / \ \ 1 4 14 \ 5 """assert t.root.left.left isnotNoneassert t.root.left.right.right isnotNoneassert t.root.left.left.label ==1assert t.root.left.right.label ==4assert t.root.left.right.right.label ==5assert t.root.left.right.left isNoneassert t.root.left.left.parent == t.root.leftassert t.root.left.right.parent == t.root.left t.remove(3)r""" 8 / \ 4 10 / \ \ 1 5 14 """assert t.root isnotNoneassert t.root.left.label ==4assert t.root.left.right.label ==5assert t.root.left.left.label ==1assert t.root.left.parent == t.rootassert t.root.left.left.parent == t.root.leftassert t.root.left.right.parent == t.root.left t.remove(4)r""" 8 / \ 5 10 / \ 1 14 """assert t.root.left isnotNoneassert t.root.left.left isnotNoneassert t.root.left.label ==5assert t.root.left.right isNoneassert t.root.left.left.label ==1assert t.root.left.parent == t.rootassert t.root.left.left.parent == t.root.leftdeftest_remove_2(self) ->None: t = self._get_binary_search_tree() t.remove(3)r""" 8 / \ 4 10 / \ \ 1 6 14 / \ / 5 7 13 """assert t.root isnotNoneassert t.root.left isnotNoneassert t.root.left.left isnotNoneassert t.root.left.right isnotNoneassert t.root.left.right.left isnotNoneassert t.root.left.right.right isnotNoneassert t.root.left.label ==4assert t.root.left.right.label ==6assert t.root.left.left.label ==1assert t.root.left.right.right.label ==7assert t.root.left.right.left.label ==5assert t.root.left.parent == t.rootassert t.root.left.right.parent == t.root.leftassert t.root.left.left.parent == t.root.leftassert t.root.left.right.left.parent == t.root.left.rightdeftest_empty(self) ->None: t = self._get_binary_search_tree() t.empty()assert t.root isNonedeftest_is_empty(self) ->None: t = self._get_binary_search_tree()assertnot t.is_empty() t.empty()assert t.is_empty()deftest_exists(self) ->None: t = self._get_binary_search_tree()assert t.exists(6)assertnot t.exists(-1)deftest_get_max_label(self) ->None: t = self._get_binary_search_tree()assert t.get_max_label()==14 t.empty()with self.assertRaises(Exception): t.get_max_label()deftest_get_min_label(self) ->None: t = self._get_binary_search_tree()assert t.get_min_label()==1 t.empty()with self.assertRaises(Exception): t.get_min_label()deftest_inorder_traversal(self) ->None: t = self._get_binary_search_tree() inorder_traversal_nodes = [i.label for i in t.inorder_traversal()]assert inorder_traversal_nodes == [1,3,4,5,6,7,8,10,13,14]deftest_preorder_traversal(self) ->None: t = self._get_binary_search_tree() preorder_traversal_nodes = [i.label for i in t.preorder_traversal()]assert preorder_traversal_nodes == [8,3,1,6,4,5,7,10,14,13]defbinary_search_tree_example() ->None:r""" Example 8 / \ 3 10 / \ \ 1 6 14 / \ / 4 7 13 \ 5 Example After Deletion 4 / \ 1 7 \ 5 """ t =BinarySearchTree() t.put(8) t.put(3) t.put(6) t.put(1) t.put(10) t.put(14) t.put(13) t.put(4) t.put(7) t.put(5)print(""" 8 / \\ 3 10 / \\ \\ 1 6 14 / \\ / 4 7 13 \\ 5 """ )print("Label 6 exists:", t.exists(6))print("Label 13 exists:", t.exists(13))print("Label -1 exists:", t.exists(-1))print("Label 12 exists:", t.exists(12))# Prints all the elements of the list in inorder traversal inorder_traversal_nodes = [i.label for i in t.inorder_traversal()]print("Inorder traversal:", inorder_traversal_nodes)# Prints all the elements of the list in preorder traversal preorder_traversal_nodes = [i.label for i in t.preorder_traversal()]print("Preorder traversal:", preorder_traversal_nodes)print("Max. label:", t.get_max_label())print("Min. label:", t.get_min_label())# Delete elementsprint("\nDeleting elements 13, 10, 8, 3, 6, 14")print(""" 4 / \\ 1 7 \\ 5 """ ) t.remove(13) t.remove(10) t.remove(8) t.remove(3) t.remove(6) t.remove(14)# Prints all the elements of the list in inorder traversal after delete inorder_traversal_nodes = [i.label for i in t.inorder_traversal()]print("Inorder traversal after delete:", inorder_traversal_nodes)# Prints all the elements of the list in preorder traversal after delete preorder_traversal_nodes = [i.label for i in t.preorder_traversal()]print("Preorder traversal after delete:", preorder_traversal_nodes)print("Max. label:", t.get_max_label())print("Min. label:", t.get_min_label())if__name__=="__main__":binary_search_tree_example()
Binary Trees
Explain and implement a Binary Tree.
A tree is a collection of nodes and edges between them.
It cannot have any cycles, which are edges that form a loop between nodes.
We also only consider rooted trees in computer science, which is a tree that has one root node that is able to access all other nodes.
For a tree to be a binary tree, each node can have a maximum of two children.
It's important to be able to identify and explain tree terminology as well. If given a tree, be able to point out each component.
root: The single node of a tree that can access every other node through edges.
parent node: A node that is connected to lower nodes in the tree. If a tree only has one node, it is not a parent node because there are no children.
child node: A node that is connected to a higher node in the tree. Every node except for the root is a child node of some parent.
sibling nodes: Nodes that have the same parent.
leaf node: A node that has no children (at the ends of the branches of the tree)
internal node: A non-leaf node (aka a parent)
path: A series of nodes that can be traveled through edges.
subtree: A smaller portion of the original tree. Any node that is not the root node is itself the root of a subtree.
Know the basics of each term
A non-empty tree has to have a root.
A tree doesn't have any parent nodes if there are no children.
What's the min/max number of parent and leaf nodes for a tree with 5 nodes?
Implementing as a balanced tree results in min number of parents and max number of leaves: 2 parents, 3 leaves
All that we need in order to implement a binary tree is a TreeNode class that can store a value and references to a left and right child. We can create a tree by assigning the left and right properties to point to other TreeNode instances:
A binary search tree is a binary tree with the added stipulation that all values to the left of a node are less than its value and all values to the right are greater than its value.
Example of a BST with an insert method. You won't be asked to implement a removal:
class BST {constructor(){ this.root = null;}insert(val, currentNode=this.root){if(!this.root) { this.root = new TreeNode(val);return;}if (val < currentNode.val) {if (!currentNode.left) { currentNode.left = new TreeNode(val);}else{ this.insert(val, currentNode.left);}}else{if (!currentNode.right) { currentNode.right = new TreeNode(val);}else{ this.insert(val, currentNode.right);}}}}
# Implement a Binary Search Tree (BST) that can insert values and check if# values are presentclassNode(object):def__init__(self,value): self.value = value self.left =None self.right =NoneclassBST(object):def__init__(self,root): self.root =Node(root)definsert(self,new_val):if(self.root.left==None):if(self.root.value>new_val): self.root.left =Node(new_val)elif(self.root.right==None):if(self.root.value<new_val): self.root.right =Node(new_val)else: current = self.rootwhile(current.left!=Noneor current.right!=None):if(current.value>new_val): current = current.leftelse: current = current.rightif(current.left==None): current.left =Node(new_val)else: current.right =Node(new_val)defsearch(self,find_val):if(self.root.left==Noneand self.root.right==Noneand self.root.value!=find_val):returnFalseelse: current = self.root val_possible =Truewhile(val_possible):if(current.value==find_val):returnTrueif(current.value<find_val): current = current.rightelse: current = current.leftif(current==None):returnFalseif(current.value<find_val and (current.right==Noneor current.right>find_val)):returnFalseif(current.value>find_val and (current.left==Noneor current.left<find_val)):returnFalse# Set up treetree =BST(4)# Insert elementstree.insert(2)tree.insert(1)tree.insert(3)tree.insert(5)# Check search# Should be Trueprint tree.search(4)# Should be Falseprint tree.search(6)
classSolution(object):deftopKFrequent(self,nums,k): number_frequency ={} frequency_list ={}for i in nums:if i notin number_frequency: number_frequency[i]=1else: number_frequency[i]+=1for key, value in number_frequency.items():if value notin frequency_list: frequency_list[value]= [key]else: frequency_list[value].append(key) result = []for i inrange(len(nums), 0, -1):if i in frequency_list: result.extend(frequency_list[i])iflen(result)>= k:breakreturn resultob1 =Solution()print(ob1.topKFrequent([1, 1, 1, 1, 2, 2, 3, 3, 3], 2))
write a function that checks to see if a given binary tree is perfectly balanced, meaning all leaf nodes are located at the same depth. Your function should return true if the tree is perfectly balanced and false otherwise.
Analyze the time and space complexity of your function.
JS Solution:
/* A recursive solution How would you solve this iteratively? */constcheckBalanced= (rootNode) => {// An empty tree is balanced by defaultif (!rootNode) returntrue;// recursive helper function to check the min depth of the treeconstminDepth= (node) => {if (!node) return0;return1+Math.min(minDepth(node.left),minDepth(node.right)); };// recursive helper function to check the max depth of the treeconstmaxDepth= (node) => {if (!node) return0;return1+Math.max(maxDepth(node.left),maxDepth(node.right)); };returnmaxDepth(rootNode) -minDepth(rootNode) ===0;};/* Some console.log tests */classBinaryTreeNode {constructor(value) {this.value = value;this.left =null;this.right =null; }insertLeft(value) {this.left =newBinaryTreeNode(value);returnthis.left; }insertRight(value) {this.right =newBinaryTreeNode(value);returnthis.right; }}constroot=newBinaryTreeNode(5);console.log(checkBalanced(root)); // should print trueroot.insertLeft(10);console.log(checkBalanced(root)); // should print falseroot.insertRight(11);console.log(checkBalanced(root)); // should print true;
# A recursive solution# How would you solve this iteratively?def checkBalanced(rootNode): # An empty tree is balanced by defaultif rootNode == None:return True # recursive helper functiontocheckthemindepthofthetreedefminDepth(node):ifnode == None:return0return1+min(minDepth(node.left),minDepth(node.right)) # recursive helper functiontocheckthemaxdepthofthetreedefmaxDepth(node):ifnode == None:return0return1+max(maxDepth(node.left),maxDepth(node.right))returnmaxDepth(rootNode) -minDepth(rootNode) ==0# Some console.log testsclassBinaryTreeNode:def__init__(self, value):self.value = valueself.left = Noneself.right = NonedefinsertLeft(self, value):self.left = BinaryTreeNode(value)returnself.leftdefinsertRight(self, value):self.right = BinaryTreeNode(value)returnself.rightroot = BinaryTreeNode(5)print(checkBalanced(root)) # shouldprintTrueroot.insertLeft(10)print(checkBalanced(root)) # shouldprintFalseroot.insertRight(11)print(checkBalanced(root)) # shouldprintTrue
Binary Search Tree from Sorted Array
Given an array that is sorted in ascending order containing unique integer elements, write a function that receives the sorted array as input and creates a valid binary search tree with minimal height.
For example, given an array [1, 2, 3, 4, 5, 6, 7], your function should return a binary search tree with the form
4
/ \
2 6
/ \ / \
1 3 5 7
Note that when we say "binary search tree" in this case, we're just talking about a tree that exhibits the expected form of a binary search tree. The tree in this case won't have an insert method that does the work of receiving a value and then inserting it in a valid spot in the binary search tree. Your function should place the values in valid spots that adhere to the rules of binary search trees, while also seeking to minimize the overall height of the tree.
Here's a BinaryTreeNode class that you can use to construct a binary search tree:
classBinaryTreeNode:def__init__(self,value): self.value = value self.left =None self.right =None
Analyze the time and space complexity of your solution.
Create a Minimal Height BST from Sorted Array
Understanding the Problem
This problem asks us to create a valid binary search tree from a sorted array of integers. More specifically, the resulting binary search tree needs to be of minimal height. Our function should return the root node of the created binary search tree.
From the given example where the input is [1, 2, 3, 4, 5, 6, 7], the expected answer is a binary search tree of height 3. This is the minimal height that can be achieved for an array of 7 seven elements. Try as we might, there's no way to construct a binary search tree containing all of these elements that has a shorter height.
Coming Up with a First Pass
A straightforward way to do this would be to take the first element of our array, call that the root, and then iterate through the rest of our array, adding those elements as nodes in the binary search tree. In pseudocode, that might look something like this:
def create_min_height_bst(sorted_arr):
root = BinaryTreeNode(sorted_arr[0])
for elem in sorted_arr:
root.insert(elem)
return root
import mathdefcreate_min_height_bst(sorted_array): left =0 right =len(sorted_array)-1returnrec_helper(sorted_array, left, right)defrec_helper(sorted_array,left,right):if left > right:returnNone midpoint = ((right - left) //2) + left root =BinaryTreeNode(sorted_array[midpoint]) root.left =rec_helper(sorted_array, left, midpoint -1) root.right =rec_helper(sorted_array, midpoint +1, right)return rootclassBinaryTreeNode:def__init__(self,value): self.value = value self.left =None self.right =None# Helper function to validate that the created tree is a valid BSTdefis_BST(root,min_bound,max_bound):if root isNone:returnTrueif root.value < min_bound or root.value > max_bound:returnFalse left =is_BST(root.left, min_bound, root.value -1) right =is_BST(root.right, root.value +1, max_bound)return left and right# Helper function to check the max height of a BSTdeffind_bst_max_height(node):if node isNone:return0return1+max(find_bst_max_height(node.left), find_bst_max_height(node.right))# Helper function to validate that the given BST exhibits the min heightdefis_bst_min_height(root,N): bst_max_height =find_bst_max_height(root) should_equal = math.floor(math.log2(N))+1return bst_max_height == should_equal# Helper function to count the number of nodes for a given BSTdefcount_bst_nodes(root,count):if root isNone:return countcount_bst_nodes(root.left, count) count +=1count_bst_nodes(root.right, count)# Some testssorted_array = [1,2,3,4,5,6,7]bst =create_min_height_bst(sorted_array)print(is_BST(bst, float("-inf"), float("inf")))# should print trueprint(is_bst_min_height(bst, len(sorted_array)))# should print truesorted_array = [4,10,11,18,42,43,47,49,55,67,79,89,90,95,98,100]bst =create_min_height_bst(sorted_array)print(is_BST(bst, float("-inf"), float("inf")))# should print trueprint(is_bst_min_height(bst, len(sorted_array)))# should print true
Another BST Implementation:
classBinarySearchTree:def__init__(self,value): self.value = value self.left =None self.right =Nonedefinsert(self,value):passdefcontains(self,target):passdefget_max(self):passdeffor_each(self,cb):pass
Find the maximum path sum between two leaves of a binary tree
Given a binary tree in which each node element contains a number. Find the maximum possible sum from one leaf node to another.
The maximum sum path may or may not go through root. For example, in the following binary tree, the maximum sum is 27(3 + 6 + 9 + 0 - 1 + 10). Expected time complexity is O(n).
If one side of root is empty, then function should return minus infinite (INT_MIN in case of C/C++)
A simple solution is to traverse the tree and do following for every traversed node X.
1) Find maximum sum from leaf to root in left subtree of X (we can use this post for this and next steps)
2) Find maximum sum from leaf to root in right subtree of X.
3) Add the above two calculated values and X->data and compare the sum with the maximum value obtained so far and update the maximum value.
4) Return the maximum value.
The time complexity of above solution is O(n2)
We can find the maximum sum using single traversal of binary tree. The idea is to maintain two values in recursive calls
(Note: If the tree is right-most or left-most tree then first we have to adjust the tree such that both the right and left are not null. Left-most means if the right of super root of the tree is null and right-most tree means if left of super root of the tree is null.)
1) Maximum root to leaf path sum for the subtree rooted under current node.
2) The maximum path sum between leaves (desired output).
For every visited node X, we find the maximum root to leaf sum in left and right subtrees of X. We add the two values with X->data, and compare the sum with maximum path sum found so far.
Python
# Python program to find maximumpath sum between two leaves# of a binary treeINT_MIN =-2**32# A binary tree nodeclassNode:# Constructor to create a new nodedef__init__(self,data): self.data = data self.left =None self.right =None# Utility function to find maximum sum between any# two leaves. This function calculates two values:# 1) Maximum path sum between two leaves which are stored# in res# 2) The maximum root to leaf path sum which is returned# If one side of root is empty, then it returns INT_MINdefmaxPathSumUtil(root,res):# Base Caseif root isNone:return0# Find maximumsum in left and righ subtree. Also# find maximum root to leaf sums in left and right# subtrees ans store them in ls and rs ls =maxPathSumUtil(root.left, res) rs =maxPathSumUtil(root.right, res)# If both left and right children existif root.left isnotNoneand root.right isnotNone:# update result if needed res[0]=max(res[0], ls + rs + root.data)# Return maximum possible value for root being# on one sidereturnmax(ls, rs)+ root.data# If any of the two children is empty, return# root sum for root being on one sideif root.left isNone:return rs + root.dataelse:return ls + root.data# The main function which returns sum of the maximum# sum path betwee ntwo leaves. THis function mainly# uses maxPathSumUtil()defmaxPathSum(root): res = [INT_MIN]maxPathSumUtil(root, res)return res[0]# Driver program to test above functionroot =Node(-15)root.left =Node(5)root.right =Node(6)root.left.left =Node(-8)root.left.right =Node(1)root.left.left.left =Node(2)root.left.left.right =Node(6)root.right.left =Node(3)root.right.right =Node(9)root.right.right.right =Node(0)root.right.right.right.left =Node(4)root.right.right.right.right =Node(-1)root.right.right.right.right.left =Node(10)print"Max pathSum of the given binary tree is",maxPathSum(root)ck_007)
Output
Max pathSum of the given binary tree is 27
Two extreme implementations:
Implementing in a chain results in max number of parents and min number of leaves: 4 parents, 1 leaf
Given a tree, be able to determine the order of each traversal type: