Binary Search Tree In Python
A Binary search tree is a binary tree where the values of the left sub-tree are less than the root node and the values of the right sub-tree are greater than the value of the root node. In this article, we will discuss the binary search tree in Python.
What is a Binary Search Tree(BST)?
A Binary Search Tree is a data structure used in computer science for organizing and storing data in a sorted manner. Each node in a Binary Search Tree has at most two children, a left child and a right child.
Properties of BST
- The left side of a node only has smaller values.
- The right side of a node only has bigger values.
- Both the left and right sides follow the same rule, forming a smaller binary search tree.
In this binary tree
- Root node: 8
- Left subtree (3): values < 8
- Right subtree (10): values > 8
- Each subtree maintains the same rule, with values relative to their respective roots.
Creation of Binary Search Tree (BST) in Python
Below, are the steps to create a Binary Search Tree (BST).
- Create a class Tree
- In the parameterized constructor, we have the default parameter val = None
- When the Tree class is instantiated without any parameter then it creates an empty node with left and right pointers
- If we pass the value while instantiating then it creates a node having that value and left, right pointers are created with None Types.
class Tree:
def __init__(self,val=None):
self.value = val
if self.value:
self.left = Tree()
self.right = Tree()
else:
self.left = None
self.right = None
#creating a node in the tree
t = Tree(5)
Time complexity: O(1)
Space complexity: O(1)
Basic Operations on Binary Search Tree (BST)
Below, are the some basic Operations of Binary Search Tree(BST) in Python.
Insertion in Binary Search Tree(BST) in Python
Inserting a node in a Binary search tree involves adding a new node to the tree while maintaining the binary search tree (BST) property. So we need to traverse through all the nodes till we find a leaf node and insert the node as the left or right child based on the value of that leaf node. So the three conditions that we need to check at the leaf node are listed below:
Illustrations
Let us insert 13 into the below Binary search tree
- When we call the insert method then 13 is checked with the root node which is 15 , Since 13 < 15 we need to insert 13 to the left sub tree. So we recursively call left_sub_tree.insert(13)
- Now the left child of 15 is 10 so 13 is checked with 10 and since 13>10 we need to insert 13 to the right sub tree. So we recursively call right_sub_tree.insert(13)
- Here, the right child of 10 is 11 so 13 is checked with 11 and since 13>11 and we need to insert 13 to the right child of 11. Again we recursively call right_sub_tree.insert(13)
- In this recursion call, we found there is no node which means we reached till the leaf node, so we create a node and insert the data in the node
After inserting 13 , the BST looks like this :
Insertion in Binary Search Tree (BST) in Python
class Tree:
def __init__(self,val=None):
# Initialize the Tree node with a value
self.value = val
# If the node has a value,
#create left and right children nodes
if self.value:
self.left = Tree()
self.right = Tree()
else:
# If the node has no value,
#set left and right children to None
self.left = None
self.right = None
# Check if the node is empty (has no value)
def isempty(self):
return (self.value == None)
# Insert a new value into the tree
def insert(self,data):
# If the node is empty, insert the data here
if self.isempty():
self.value = data
# Create left and right children
#for the inserted node
self.left = Tree()
self.right = Tree()
print("{} is inserted successfully".format(self.value))
# If data is less than current node value,
#insert into left subtree
elif data < self.value:
self.left.insert(data)
return
# If data is greater than current node value,
#insert into right subtree
elif data > self.value:
self.right.insert(data)
# If data is equal to current node value, do nothing
elif data == self.value:
return
# Create a tree with root value 15
t = Tree(15)
# Insert some values into the tree
t.insert(10)
t.insert(18)
t.insert(4)
t.insert(11)
t.insert(16)
t.insert(20)
t.insert(13)
Output
10 is inserted successfully 18 is inserted successfully 4 is inserted successfully 11 is inserted successfully 16 is inserted successfully 20 is inserted successfully 13 is inserted successfully
Time complexity: O(log n)
Space complexity: O(n)
Searching in Binary Search Tree in Python
Searching in Binary Search Tree is very easy because we don’t need to traverse all the nodes. It is based on the value of nodes,which means if the value is less than root then we search in the left sub tree or else we search in right sub tree.
Illustrations
Steps to search a value ‘v’ in BST:
- The value is checked with the root node, if it is equal to the root node then it returns the value
- If the value is less than root node then it recursively calls the search operation in the left sub tree
- If the value is greater than root node then it recursively calls the search operation in the right sub tree
- In above example BST search for value ‘8’ begins from the root node.
- If the value is not equal to the current node’s value, the search continues recursively on the left subtree if the value is less than the current node’s value.
- Once the value matches a node, the search terminates, returning the node containing the value.
Searching in Binary Search Tree (BST) in Python
class Tree:
def __init__(self, val=None):
# Initialize a tree node with a value
self.value = val
if self.value:
# If a value is provided,
#create left and right children as empty trees
self.left = Tree()
self.right = Tree()
else:
# If no value is provided, set left and
#right children to None
self.left = None
self.right = None
def isempty(self):
# Check if the tree node is empty
return self.value == None
def isleaf(self):
# Check if the tree node is a leaf node (both left and right children are None)
if self.left.left == None and self.right.right == None:
return True
else:
return False
def insert(self, data):
if self.isempty():
# If the current node is empty,
#insert the data as its value
self.value = data
# Create empty left and right children
self.left = Tree()
self.right = Tree()
elif self.value == data:
# If the data already exists in the tree, return
return
elif data < self.value:
# If the data is less than the current node's value,
#insert it into the left subtree
self.left.insert(data)
return
elif data > self.value:
# If the data is greater than the current node's value,
#insert it into the right subtree
self.right.insert(data)
return
def find(self, v):
if self.isempty():
# If the tree is empty, the value is not found
print("{} is not found".format(v))
return False
if self.value == v:
# If the value is found at the current node,
#print a message and return True
print("{} is found".format(v))
return True
if v < self.value:
# If the value is less than the current node's value,
#search in the left subtree
return self.left.find(v)
else:
# If the value is greater than the current node's value,
#search in the right subtree
return self.right.find(v)
def inorder(self):
if self.isempty():
# If the tree is empty, return an empty list
return []
else:
# Return the inorder traversal of the tree (left subtree, root, right subtree)
return self.left.inorder() + [self.value] + self.right.inorder()
# Example usage
t = Tree(20)
t.insert(15)
t.insert(25)
t.insert(8)
t.insert(16)
t.find(8)
print(t.inorder())
Output
8 is found [8, 15, 16, 20, 25]
Time complexity: O(log n)
Space complexity: O(n)
Deletion in Binary Search Tree in Python
Deleting a node in Binary search tree involves deleting an existing node while maintaining the properties of Binary Search Tree(BST). we need to search the node which needs to be deleted and then delete it.
Case 1 : While deleting a node having both left child and right child
- After finding the node to be deleted, copy the value of right child to v and copy the right child’s left pointer to left of v and right pointer
- In the above example, suppose we need to delete 18 then we need to replace 18 with the maximum value of its left or right sub tree
- Let us replace it with 20 which is maximum value of right sub tree
- So after deleting 18, the Binary search tree looks like this
Case 2 : While deleting a node having either left child or right child
- After finding the node to be deleted, we need to replace the value in the node with its left node it has left child or right node if it has right child.
- In the above example, suppose we need to delete 16 then we need to replace 16 with the value of its right child node
- So it will be replaced with 17 which is the value of its right child node
- So after deleting 16, the Binary search tree looks like this
Case 3 : While deleting a leaf node(a node which has no child)
- In the above example, suppose we need to delete 4 which is a leaf node.
- Simply we change value of the node, left and right pointers to None.
- After deleting 4 the BST looks like this:
Deletion in Binary Search Tree in Python
class Tree:
def __init__(self, val=None):
# Initialize the Tree node with a value
self.value = val
# Create left and right child nodes
#if the value is not None
if self.value:
self.left = Tree()
self.right = Tree()
else:
self.left = None
self.right = None
def isempty(self):
# Check if the node is empty (value is None)
return (self.value == None)
def insert(self, data):
# Insert a new value into the Tree
if self.isempty():
self.value = data
self.left = Tree()
self.right = Tree()
elif self.value == data:
return
elif data < self.value:
self.left.insert(data)
return
elif data > self.value:
self.right.insert(data)
return
def isleaf(self):
# Check if the node is a leaf node (no children)
if self.left == None and self.right == None:
return True
else:
return False
def maxval(self):
# Find the maximum value in the Tree
if self.right.right == None:
return (self.value)
else:
return (self.right.maxval())
def delete(self, v):
# Delete a value from the Tree
if self.isempty():
return
if v < self.value:
self.left.delete(v)
return
if v > self.value:
self.right.delete(v)
return
if v == self.value:
if self.isleaf():
self.value = None
self.left = None
self.right = None
return
elif self.left.isempty():
self.value = self.right.value
self.left = self.right.left
self.right = self.right.right
return
else:
self.value = self.left.maxval()
self.left.delete(self.left.maxval())
return
def inorder(self):
# Traverse the Tree in inorder (left-root-right)
#and return the values
if self.isempty():
return ([])
else:
return (self.left.inorder() + [self.value] + self.right.inorder())
# Test the Tree class
t = Tree(15)
t.insert(10)
t.insert(18)
t.insert(4)
t.insert(11)
t.insert(16)
t.insert(20)
print("Before deleting 4: ")
print(t.inorder())
t.delete(4)
print("After deleting 4: ")
print(t.inorder())
Output
Before deleting 4: [4, 10, 11, 15, 16, 18, 20] After deleting 4: [10, 11, 15, 16, 18, 20]
Time complexity: O(log n)
Space complexity: O(1)
Traversals in Binary Search Tree in Python
Below, are the traversals of Binary Search Tree (BST) in Python which we can perform as SImilar to Binary Tree.
Traversals in BST in Python
class Tree:
def __init__(self, val=None):
# Initialize the node with a value and left and right subtrees
self.value = val
if self.value:
self.left = Tree()
self.right = Tree()
else:
self.left = None
self.right = None
def isempty(self):
# Check if the tree is empty
return self.value is None
def insert(self, data):
# Insert a value into the tree
if self.isempty():
self.value = data
self.left = Tree()
self.right = Tree()
elif self.value == data:
return
elif data < self.value:
self.left.insert(data)
elif data > self.value:
self.right.insert(data)
def isleaf(self):
# Check if the node is a leaf node
return self.left is None and self.right is None
def inorder(self):
# Perform inorder traversal
if self.isempty():
return []
else:
return self.left.inorder() + [self.value] + self.right.inorder()
# Example usage
t = Tree(10)
t.insert(8)
t.insert(6)
t.insert(12)
print("Inorder Traversal : ")
print(t.inorder())
Output
Inorder Traversal : [6, 8, 10, 12]
Time complexity: O(log n)
Space complexity: O(n)
Applications of BST:
- Graph algorithms: BSTs can be used to implement graph algorithms, such as in minimum spanning tree algorithms.
- Priority Queues: BSTs can be used to implement priority queues, where the element with the highest priority is at the root of the tree, and elements with lower priority are stored in the subtrees.
- Self-balancing binary search tree: BSTs can be used as a self-balancing data structures such as AVL tree and Red-black tree.
- Data storage and retrieval: BSTs can be used to store and retrieve data quickly, such as in databases, where searching for a specific record can be done in logarithmic time.
Advantages:
- Fast search: Searching for a specific value in a BST has an average time complexity of O(log n), where n is the number of nodes in the tree. This is much faster than searching for an element in an array or linked list, which have a time complexity of O(n) in the worst case.
- In-order traversal: BSTs can be traversed in-order, which visits the left subtree, the root, and the right subtree. This can be used to sort a dataset.
- Space efficient: BSTs are space efficient as they do not store any redundant information, unlike arrays and linked lists.
Disadvantages:
- Skewed trees: If a tree becomes skewed, the time complexity of search, insertion, and deletion operations will be O(n) instead of O(log n), which can make the tree inefficient.
- Additional time required: Self-balancing trees require additional time to maintain balance during insertion and deletion operations.
- Efficiency: BSTs are not efficient for datasets with many duplicates as they will waste space.
Contact Us