Topic 1: Introduction To Binary Search Trees
Topic 1: Introduction To Binary Search Trees
Binary Search Tree is a node-based binary tree data structure which has the
following properties:
● The left subtree of a node contains only nodes with keys lesser than or equal
to the node's key.
● The right subtree of a node contains only nodes with keys greater than the
node's key.
● The left and right subtree each must also be a binary search tree.
There must be no duplicate nodes.
The above properties of Binary Search Tree provide an ordering among keys so that
the operations like search, minimum and maximum can be done fast in comparison
to normal Binary Trees. If there is no ordering, then we may have to compare every
key to search a given key.
Searching a Key
Using the property of Binary Search Tree, we can search for an element in O(h) time
complexity where h is the height of the given BST.
To search a given key in Binary Search Tree, first compare it with root, if the key is
present at root, return root. If the key is greater than the root's key, we recur for the
right subtree of the root node. Otherwise, we recur for the left subtree.
Implementation:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// C++ function to search a given key in a given BST
/* Node Structure:
struct Node
{
int key;
Node* left, right;
};
*/
struct node* search(struct node* root, int key)
{
// Base Cases: root is null or key is present at root
if (root == NULL || root->key == key)
return root;
Insertion of a Key
Inserting a new node in the Binary Search Tree is always done at the leaf nodes to
maintain the order of nodes in the Tree. The idea is to start searching the given node
to be inserted from the root node till we hit a leaf node. Once a leaf node is found,
the new node is added as a child of the leaf node.
For Example:
100 100
/ \ Insert 40 / \
20 500 ---------> 20 500
/ \ / \
10 30 10 30
\
40
Implementation:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// C++ program to demonstrate insert
// operation in binary search tree
#include<bits/stdc++.h>
using namespace std;
// Binary Search Tree node
struct node
{
int key;
struct node *left, *right;
};
// A utility function to create a new BST node
struct node *newNode(int item)
{
node *temp = new node;
temp->key = item;
temp->left = temp->right = NULL;
return temp;
}
// A utility function to do inorder traversal of BST
void inorder(struct node *root)
{
if (root != NULL)
{
inorder(root->left);
cout<<root->key<<" ";
inorder(root->right);
Run
Java
Output:
20 30 40 50 60 70 80
Time Complexity: The worst case time complexity of search and insert operations is
O(h) where h is the height of the Binary Search Tree. In the worst case, we may
have to travel from root to the deepest leaf node. The height of a skewed tree may
become n and the time complexity of search and insert operation may become O(n).
Topic 2 : Deletion in a Binary Search Tree
Given a Binary Search Tree and a node to be deleted. The task is to search that
node in the given BST and delete it from the BST if it is present.
2. Node to be deleted has only one child: Copy the child to the node and
delete the child.
3. Node to be deleted has two children: Find the successor of the node. Copy
contents of the inorder successor to the node and delete the inorder
successor. Note that inorder predecessor can also be used.
Note: The inorder successor can be obtained by finding the minimum value in the
right child of the node.
Below is the implementation of the above three cases:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// C++ program to demonstrate delete
// operation in binary search tree
#include<bits/stdc++.h>
using namespace std;
// BST Node
struct node
{
int key;
struct node *left, *right;
};
// A utility function to create a new BST node
struct node *newNode(int item)
{
node *temp = new node;
temp->key = item;
temp->left = temp->right = NULL;
return temp;
}
// A utility function to do inorder traversal of BST
void inorder(struct node *root)
{
if (root != NULL)
{
inorder(root->left);
cout<<root->key<<" ";
inorder(root->right);
}
Run
Java
Output:
Inorder traversal of the given tree
20 30 40 50 60 70 80
Delete 20
Inorder traversal of the modified tree
30 40 50 60 70 80
Delete 30
Inorder traversal of the modified tree
40 50 60 70 80
Delete 50
Inorder traversal of the modified tree
40 60 70 80
Illustration:
Topic 3 : Finding LCA in a Binary Search Tree
We have already seen the definition of LCA and finding LCA of any two nodes in a
Binary Tree.
Problem: Given values of two values n1 and n2 in a Binary Search Tree, find
the Lowest Common Ancestor (LCA). For Simplicity, you may assume that both the
values exist in the tree.
LCA of 10 and 14 is 12
LCA of 14 and 8 is 8
LCA of 10 and 22 is 20
Finding LCA
Since a Binary Search Tree is also a Binary Tree, we can apply the same process of
finding LCA of two nodes in BST as that of binary trees. But finding LCA in a Binary
Tree takes O(N) time complexity.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// A recursive CPP program to find
// LCA of two nodes n1 and n2.
#include <bits/stdc++.h>
using namespace std;
class node
{
public:
int data;
node* left, *right;
};
// Function to find LCA of n1 and n2.
// The function assumes that both
// n1 and n2 are present in BST
node *lca(node* root, int n1, int n2)
{
if (root == NULL) return NULL;
// If both n1 and n2 are smaller
// than root, then LCA lies in left
if (root->data > n1 && root->data > n2)
return lca(root->left, n1, n2);
// If both n1 and n2 are greater than
// root, then LCA lies in right
if (root->data < n1 && root->data < n2)
return lca(root->right, n1, n2);
Run
Java
Output:
LCA of 10 and 14 is 12
LCA of 14 and 8 is 8
LCA of 10 and 22 is 20
The time complexity of the above solution is O(h) where h is the height of the tree.
Also, the above solution requires O(h) extra space in function call stack for recursive
function calls. We can avoid extra space using an iterative solution.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* Function to find LCA of n1 and n2.
The function assumes that both
n1 and n2 are present in BST */
struct node *lca(struct node* root, int n1, int n2)
{
while (root != NULL)
{
// If both n1 and n2 are smaller than root,
// then LCA lies in left subtree
if (root->data > n1 && root->data > n2)
root = root->left;
// If both n1 and n2 are greater than root,
// then LCA lies in the right subtree
else if (root->data < n1 && root->data < n2)
root = root->right;
else break;
}
return root;
}
Topic 4 : Introduction to AVL Tree
AVL tree is a self-balancing Binary Search Tree (BST) where the difference between
heights of left and right subtrees cannot be more than one for all nodes.
The above tree is AVL because differences between heights of left and right
subtrees for every node is less than or equal to 1.
The above tree is not AVL because differences between heights of left and right
subtrees for 8 and 18 is greater than 1.
Why AVL Trees?
Most of the BST operations (e.g., search, max, min, insert, delete.. etc) take O(h)
time where h is the height of the BST. The cost of these operations may become
O(n) for a skewed Binary tree. If we make sure that the height of the tree remains
O(Logn) after every insertion and deletion, then we can guarantee an upper bound of
O(Logn) for all these operations. The height of an AVL tree is always O(Logn) where
n is the number of nodes in the tree.
Insertion
To make sure that the given tree remains AVL after every insertion, we must
augment the standard BST insert operation to perform some re-balancing. Following
are two basic operations that can be performed to re-balance a BST without violating
the BST property (keys(left) < key(root) < keys(right)).
1. Left Rotation
2. Right Rotation
Steps to follow for insertion: Let the newly inserted node be w.
Following are the operations to be performed in the above mentioned 4 cases. In all
of the cases, we only need to re-balance the subtree rooted with z and the complete
tree becomes balanced as the height of subtree (After appropriate rotations) rooted
with z becomes the same as it was before insertion.