0% found this document useful (0 votes)
7 views81 pages

Dsa Unit 3

The document provides an overview of tree data structures, explaining their hierarchical organization and key terminologies such as parent, child, root, and leaf nodes. It details various types of trees, including binary, ternary, and N-ary trees, along with basic operations like creation, insertion, searching, and traversal techniques such as depth-first and breadth-first search. Additionally, it discusses the importance of tree structures in representing hierarchical data and outlines specific traversal methods with examples.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views81 pages

Dsa Unit 3

The document provides an overview of tree data structures, explaining their hierarchical organization and key terminologies such as parent, child, root, and leaf nodes. It details various types of trees, including binary, ternary, and N-ary trees, along with basic operations like creation, insertion, searching, and traversal techniques such as depth-first and breadth-first search. Additionally, it discusses the importance of tree structures in representing hierarchical data and outlines specific traversal methods with examples.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 81

UNIT – 3

TREE ADT:
Tree data structure is a specialized data structure to store data in
hierarchical manner. It is used to organize and store data in the
computer to be used more effectively. It consists of a central node,
structural nodes, and sub-nodes, which are connected via edges. We
can also say that tree data structure has roots, branches, and leaves
connected.

What is Tree Data Structure?


Tree data structure is a hierarchical structure that is used to represent
and organize data in a way that is easy to navigate and search. It is a
collection of nodes that are connected by edges and has a hierarchical
relationship between the nodes.
The topmost node of the tree is called the root, and the nodes below it
is called the child nodes. Each node can have multiple child nodes, and
these child nodes can also have their own child nodes, forming a
recursive structure.
Why Tree is considered a non-linear data structure?
The data in a tree are not stored in a sequential manner i.e., they are
not stored linearly. Instead, they are arranged on multiple levels or we
can say it is a hierarchical structure. For this reason, the tree is
considered to be a non-linear data structure.

Basic Terminologies In Tree Data Structure:


• Parent Node: The node which is a predecessor of a node is
called the parent node of that node. {B} is the parent node
of {D, E}.
• Child Node: The node which is the immediate successor of
a node is called the child node of that node. Examples: {D,
E} are the child nodes of {B}.
• Root Node: The topmost node of a tree or the node which
does not have any parent node is called the root node. {A} is
the root node of the tree. A non-empty tree must contain
exactly one root node and exactly one path from the root to all
other nodes of the tree.
• Leaf Node or External Node: The nodes which do not
have any child nodes are called leaf nodes. {I, J, K, F, G,
H} are the leaf nodes of the tree.
• Ancestor of a Node: Any predecessor nodes on the path
of the root to that node are called Ancestors of that node. {A,
B} are the ancestor nodes of the node {E}
• Descendant: A node x is a descendant of another node y if
and only if y is an ancestor of x.
• Sibling: Children of the same parent node are called
siblings. {D, E} are called siblings.
• Level of a node: The count of edges on the path from the
root node to that node. The root node has level 0.
• Internal node: A node with at least one child is called
Internal Node.
• Neighbour of a Node: Parent or child nodes of that node
are called neighbours of that node.
• Subtree: Any node of the tree along with its descendant.
Representation of Tree Data Structure:
A tree consists of a root node, and zero or more subtrees T1, T2, … ,
Tk such that there is an edge from the root node of the tree to the root
node of each subtree. Subtree of a node X consists of all the nodes
which have node X as the ancestor node.

Representation of a Node in Tree Data Structure:


class Node {
public:
int data;
Node* first_child;
Node* second_child;
Node* third_child;
.
.
.
Node* nth_child;
};

Importance for Tree Data Structure:


1. One reason to use trees might be because you want to store
information that naturally forms a hierarchy. For example, the file
system on a computer.
2. Trees (with some ordering e.g., BST) provide moderate
access/search (quicker than Linked List and slower than arrays).
3. Trees provide moderate insertion/deletion (quicker than Arrays
and slower than Unordered Linked Lists).
4. Like Linked Lists and unlike Arrays, Trees don’t have an upper
limit on the number of nodes as nodes are linked using pointers.

Types of Tree data structures:


Tree data structure can be classified into three types based upon the
number of children each node of the tree can have. The types are:
• Binary tree: In a binary tree, each node can have a
maximum of two children linked to it. Some common types of
binary trees include full binary trees, complete binary trees,
balanced binary trees, and degenerate or pathological binary
trees.
• Ternary Tree: A Ternary Tree is a tree data structure in
which each node has at most three child nodes, usually
distinguished as “left”, “mid” and “right”.
• N-ary Tree or Generic Tree: Generic trees are a
collection of nodes where each node is a data structure that
consists of records and a list of references to its children
(duplicate references are not allowed). Unlike the linked list,
each node stores the address of multiple nodes.
Basic Operations of Tree Data Structure:
• Create – create a tree in the data structure.
• Insert − Inserts data in a tree.
• Search − Searches specific data in a tree to check whether it is
present or not.
• Traversal:
o Depth-First-Search Traversal
o Breadth-First-Search Traversal

Tree Traversal Techniques


Tree Traversal techniques include various ways to visit all the nodes
of the tree. Unlike linear data structures (Array, Linked List, Queues,
Stacks, etc) which have only one logical way to traverse them, trees
can be traversed in different ways. In this article, we will discuss about
all the tree traversal techniques along with their uses.

Tree Traversal refers to the process of visiting or accessing each node


of the tree exactly once in a certain order. Tree traversal algorithms
help us to visit and process all the nodes of the tree. Since tree is not a
linear data structure, there are multiple nodes which we can visit after
visiting a certain node. There are multiple tree traversal techniques
which decide the order in which the nodes of the tree are to be visited.

Tree Traversal Techniques:

A Tree Data Structure can be traversed in following ways:


• Depth First Search or DFS
o Inorder Traversal
o Preorder Traversal
o Postorder Traversal
• Level Order Traversal or Breadth First Search or BFS
Inorder Traversal:
Inorder traversal visits the node in the order: Left -> Root -> Right
Algorithm for Inorder Traversal:
Inorder(tree)
• Traverse the left subtree, i.e., call Inorder(left->subtree)
• Visit the root.
• Traverse the right subtree, i.e., call Inorder(right->subtree)

Uses of Inorder Traversal:


• In the case of binary search trees (BST), Inorder traversal
gives nodes in non-decreasing order.
• To get nodes of BST in non-increasing order, a variation of
Inorder traversal where Inorder traversal is reversed can be
used.
Example:
struct Node {
int data;
Node* left;
Node* right;
Node(int val) : data(val), left(nullptr), right(nullptr) {}
};
void inorderTraversal(Node* root) {

if (root == nullptr)
return;
inorderTraversal(root->left);
cout << root->data << " ";
inorderTraversal(root->right);
}
int main() {
Node* root = new Node(1);
root->left = new Node(2);
root->right = new Node(3);
root->left->left = new Node(4);
root->left->right = new Node(5);
inorderTraversal(root);
return 0;
}

Output
42513

Time Complexity: O(N)


Auxiliary Space: If we don’t consider the size of the stack for function
calls then O (1) otherwise O(h) where h is the height of the tree.

Preorder Traversal:
Preorder traversal visits the node in the order: Root -> Left -> Right
Algorithm for Preorder Traversal:
Preorder(tree)
• Visit the root.
• Traverse the left subtree, i.e., call Preorder(left->subtree)
• Traverse the right subtree, i.e., call Preorder(right->subtree)

Uses of Preorder Traversal:


• Preorder traversal is used to create a copy of the tree.
• Preorder traversal is also used to get prefix expressions on an
expression tree.
Example:
struct Node {
int data;
Node* left;
Node* right;
Node(int x) {
data = x;
left = right = nullptr;
}
};
void preorderTraversal(Node* root) {
if (root == nullptr)
return;
cout << root->data << " ";
preorderTraversal(root->left);
preorderTraversal(root->right);
}
int main() {
Node* root = new Node(1);
root->left = new Node(2);
root->right = new Node(3);
root->left->left = new Node(4);
root->left->right = new Node(5);
preorderTraversal(root);
return 0;
}

Output
12453

Time Complexity: O(N)


Auxiliary Space: If we don’t consider the size of the stack for function
calls then O (1) otherwise O(h) where h is the height of the tree.

Postorder Traversal:
Postorder traversal visits the node in the order: Left -> Right -> Root
Algorithm for Postorder Traversal:
Algorithm Postorder(tree)
• Traverse the left subtree, i.e., call Postorder(left->subtree)
• Traverse the right subtree, i.e., call Postorder(right->subtree)
• Visit the root

Uses of Postorder Traversal:


• Postorder traversal is used to delete the tree. Postorder
traversal is also useful to get the postfix expression of an
expression tree.
• Postorder traversal can help in garbage collection algorithms,
particularly in systems where manual memory management is
used.
Example:
struct Node {
int data;
Node* left;
Node* right;
Node(int x) {
data = x;
left = right = nullptr;
}
};
void postorderTraversal(Node* node) {
if (node == nullptr)
return;
postorderTraversal(node->left);
postorderTraversal(node->right);
cout << node->data << " ";
}
int main() {
Node* root = new Node(1);
root->left = new Node(2);
root->right = new Node(3);
root->left->left = new Node(4);
root->left->right = new Node(5);
postorderTraversal(root);
return 0;
}

Output
45231

Level Order Traversal :


Level Order Traversal visits all nodes present in the same level
completely before visiting the next level.
Algorithm for Level Order Traversal:
LevelOrder(tree)
• Create an empty queue Q
• Enqueue the root node of the tree to Q
• Loop while Q is not empty
o Dequeue a node from Q and visit it
o Enqueue the left child of the dequeued node if it
exists
o Enqueue the right child of the dequeued node if
it exists

Uses of Level Order:


• Level Order Traversal is mainly used as Breadth First Search
to search or process nodes level-by-level.
• Level Order traversal is also used for Tree Serialization and
Deserialization .
Example:
struct Node {
int data;
Node* left;
Node* right;
Node(int x) {
data = x;
left = right = nullptr;
}
};
void levelOrderTraversal(Node* root) {
if (!root) return;
queue<Node*> q;
q.push(root);
while (!q.empty()) {
Node* curr = q.front();
q.pop();
cout << curr->data << " ";
if (curr->left) q.push(curr->left);
if (curr->right) q.push(curr->right);
}
}
int main() {
Node* root = new Node(1);
root->left = new Node(2);
root->right = new Node(3);
root->left->left = new Node(4);
root->left->right = new Node(5);
root->right->right = new Node(6);
levelOrderTraversal(root);
return 0;
}

Output
123456

BINARY TREE:
Binary Tree is a non-linear data structure where each node has at
most two children. In this article, we will cover all the basics of Binary
Tree, Operations on Binary Tree, its implementation, advantages,
disadvantages which will help you solve all the problems based on
Binary Tree.

What is Binary Tree?


Binary tree is a tree data structure(non-linear) in which each node
can have at most two children which are referred to as the left
child and the right child.
The topmost node in a binary tree is called the root, and the bottom-
most nodes are called leaves. A binary tree can be visualized as a
hierarchical structure with the root at the top and the leaves at the
bottom.
Representation of Binary Tree
Each node in a Binary Tree has three parts:
• Data
• Pointer to the left child
• Pointer to the right child

Syntax:
class Node {
public:
int data;
Node* left, * right;
Node(int key) {
data = key;
left = nullptr;
right = nullptr;
}
};

Example for Creating a Binary Tree


Here’s an example of creating a Binary Tree with four nodes (2, 3, 4, 5)

Example:
struct Node{
int data;
Node *left, *right;
Node(int d){
data = d;
left = NULL;
right = NULL;
}
};
int main(){
Node* firstNode = new Node(2);
Node* secondNode = new Node(3);
Node* thirdNode = new Node(4);
Node* fourthNode = new Node(5);
firstNode->left = secondNode;
firstNode->right = thirdNode;
secondNode->left = fourthNode;
return 0;
}

In the above code, we have created four tree


nodes firstNode, secondNode, thirdNode and fourthNode having
values 2, 3, 4 and 5 respectively.
• After creating three nodes, we have connected these node to
form the tree structure like mentioned in above image.
• Connect the secondNode to the left
of firstNode by firstNode->left = secondNode
• Connect the thirdNode to the right of firstNode by firstNode-
>right = thirdNode
• Connect the fourthNode to the left
of secondNode by secondNode->left = fourthNode
Terminologies in Binary Tree
• Nodes: The fundamental part of a binary tree, where each
node contains data and link to two child nodes.
• Root: The topmost node in a tree is known as the root node.
It has no parent and serves as the starting point for all nodes in
the tree.
• Parent Node: A node that has one or more child nodes. In a
binary tree, each node can have at most two children.
• Child Node: A node that is a descendant of another node
(its parent).
• Leaf Node: A node that does not have any children.
• Internal Node: A node that has at least one child. This
includes all nodes except the root and the leaf nodes.
• Depth of a Binary Tree: The number of edges from a
specific node to the root node. The depth of the root node is
zero.
• Height of a Binary Tree: The number of nodes from the
deepest leaf node to the root node.

Operations On Binary Tree


Following is a list of common operations that can be performed on a
binary tree:
1. Traversal in Binary Tree
Traversal in Binary Tree involves visiting all the nodes of the binary
tree. Tree Traversal algorithms can be classified broadly into two
categories, DFS and BFS:
Depth-First Search (DFS) algorithms: DFS explores as far
down a branch as possible before backtracking. It is implemented using
recursion. The main traversal methods in DFS for binary trees are:
• Preorder Traversal (current-left-right): Visits the node first,
then left subtree, then right subtree.
• Inorder Traversal (left-current-right): Visits left subtree,
then the node, then the right subtree.
• Postorder Traversal (left-right-current): Visits left subtree,
then right subtree, then the node.
Breadth-First Search (BFS) algorithms: BFS explores all
nodes at the present depth before moving on to nodes at the next
depth level. It is typically implemented using a queue. BFS in a binary
tree is commonly referred to as Level Order Traversal.
Example:
struct Node {
int data;
Node* left, * right;
Node(int d) {
data = d;
left = nullptr;
right = nullptr;
}
};
void inOrderDFS(Node* node) {
if (node == nullptr) return;
inOrderDFS(node->left);
cout << node->data << " ";
inOrderDFS(node->right);
}
void preOrderDFS(Node* node) {
if (node == nullptr) return;
cout << node->data << " ";
preOrderDFS(node->left);
preOrderDFS(node->right);
}
void postOrderDFS(Node* node) {
if (node == nullptr) return;

postOrderDFS(node->left);
postOrderDFS(node->right);
cout << node->data << " ";
}
void BFS(Node* root) {
if (root == nullptr) return;
queue<Node*> q;
q.push(root);
while (!q.empty()) {
Node* node = q.front();
q.pop();
cout << node->data << " ";
if (node->left != nullptr) q.push(node->left);
if (node->right != nullptr) q.push(node->right);
}
}
int main() {
Node* root = new Node(2);
root->left = new Node(3);
root->right = new Node(4);
root->left->left = new Node(5);
cout << "In-order DFS: ";
inOrderDFS(root);
cout << "\nPre-order DFS: ";
preOrderDFS(root);
cout << "\nPost-order DFS: ";
postOrderDFS(root);
cout << "\nLevel order: ";
BFS(root);
return 0;
}

Output
In-order DFS: 5 3 2 4

Pre-order DFS: 2 3 5 4

Post-order DFS: 5 3 4 2

Level order: 2 3 4 5

2. Insertion in Binary Tree


Inserting elements means add a new node into the binary tree. As we
know that there is no such ordering of elements in the binary tree, So
we do not have to worry about the ordering of node in the binary tree.
We would first create a root node in case of empty tree. Then
subsequent insertions involve iteratively searching for an empty place
at each level of the tree. When an empty left or right child is found
then new node is inserted there. By convention, insertion always starts
with the left child node.
Example:
struct Node {
int data;
Node* left, * right;
Node(int k) {
data = k;
left = right = nullptr;
}
};
Node* insert(Node* root, int key) {
if (root == nullptr) {
root = new Node(key);
return root;
}
queue<Node*> q;
q.push(root);
while (!q.empty()) {
Node* temp = q.front();
q.pop();
if (temp->left == nullptr) {
temp->left = new Node(key);
break;
} else {
q.push(temp->left);
}
if (temp->right == nullptr) {
temp->right = new Node(key);
break;
} else {
q.push(temp->right);
}
}
return root;
}
void inorder(Node* root) {
if (root == nullptr) return;
inorder(root->left);
cout << root->data << " ";
inorder(root->right);
}
int main() {
Node* root = new Node(2);
root->left = new Node(3);
root->right = new Node(4) ;
root->left->left = new Node(5);
cout << "Inorder traversal before insertion: ";
inorder(root);
cout << endl;
int key = 6;
root = insert(root, key);
cout << "Inorder traversal after insertion: ";
inorder(root);
cout << endl;
return 0;
}
Output
Inorder traversal before insertion: 5 3 2 4

Inorder traversal after insertion: 5 3 6 2 4

3. Searching in Binary Tree


Searching for a value in a binary tree means looking through the tree
to find a node that has that value. Since binary trees do not have a
specific order like binary search trees, we typically use any traversal
method to search. The most common methods are depth-first search
(DFS) and breadth-first search (BFS). In DFS, we start from
the root and explore the depth nodes first. In BFS, we explore all the
nodes at the present depth level before moving on to the nodes at the
next level. We continue this process until we either find the node with
the desired value or reach the end of the tree. If the tree is empty or the
value isn’t found after exploring all possibilities, we conclude that the
value does not exist in the tree.
Example:
struct Node{
int data;
Node *left, *right;
Node(int k){
data = k;
left = right = nullptr;
}
};
bool searchDFS(Node *root, int value){
if (root == nullptr) return false;
if (root->data == value) return true;
bool left_res = searchDFS(root->left, value);
bool right_res = searchDFS(root->right, value);
return left_res || right_res;
}
int main()
{
Node *root = new Node(2);
root->left = new Node(3);
root->right = new Node(4);
root->left->left = new Node(5);
root->left->right = new Node(6);
int value = 6;
if (searchDFS(root, value))
cout << value << " is found in the binary tree" << endl;
else
cout << value << " is not found in the binary tree" << endl;
return 0;
}

Output
6 is found in the binary tree

4. Deletion in Binary Tree


Deleting a node from a binary tree means removing a specific node
while keeping the tree’s structure. First, we need to find the node that
want to delete by traversing through the tree using any traversal
method. Then replace the node’s value with the value of the last node
in the tree (found by traversing to the rightmost leaf), and then delete
that last node. This way, the tree structure won’t be affected. And
remember to check for special cases, like trying to delete from an
empty tree, to avoid any issues.
Note: There is no specific rule of deletion but we always make sure
that during deletion the binary tree proper should be preserved.
Example:
struct Node {
int data;
Node* left, * right;
Node(int k) {
data = k;
left = right = nullptr;
}
};
Node* deleteNode(Node* root, int val) {
if (root == nullptr) return nullptr;
queue<Node*> q;
q.push(root);
Node* target = nullptr;
while (!q.empty()) {
Node* curr = q.front();
q.pop();
if (curr->data == val) {
target = curr;
break;
}
if (curr->left) q.push(curr->left);
if (curr->right) q.push(curr->right);
}
if (target == nullptr) return root;
pair<Node*, Node*> last = {nullptr, nullptr};
queue<pair<Node*, Node*>> q1;
q1.push({root, nullptr});
while (!q1.empty()) {
auto curr = q1.front();
q1.pop();
last = curr;
if (curr.first->left)
q1.push({curr.first->left, curr.first});
if (curr.first->right)
q1.push({curr.first->right, curr.first});
}
Node* lastNode = last.first;
Node* lastParent = last.second;
target->data = lastNode->data;
if (lastParent) {
if (lastParent->left==lastNode)lastParent->left = nullptr;
else lastParent->right = nullptr;
delete lastNode;
} else {
delete lastNode;
return nullptr;
}
return root;
}
void inOrder(Node* root) {
if (root == nullptr) return;
inOrder(root->left);
cout << root->data << " ";
inOrder(root->right);
}
int main() {
Node *root = new Node(2);
root->left = new Node(3);
root->right = new Node(4);
root->left->left = new Node(5);
root->left->right = new Node(6);
cout << "Original tree (in-order): ";
inOrder(root);
int valToDel = 3;
root = deleteNode(root, valToDel);
cout <<"\nTree after deleting " << valToDel << " (in-order): ";
inOrder(root);
cout << endl;
return 0;
}

Output
Original tree (in-order): 5 3 6 2 4

Tree after deleting 3 (in-order): 5 6 2 4

Complexity Analysis of Binary Tree Operations


Here’s the complexity analysis for specific binary tree operations:

Operation Time Complexity Auxiliary Space

In-Order Traversal O(n) O(n)

Pre-Order Traversal O(n) O(n)

Post-Order Traversal O(n) O(n)


Operation Time Complexity Auxiliary Space

Insertion (Unbalanced) O(n) O(n)

Searching (Unbalanced) O(n) O(n)

Deletion (Unbalanced) O(n) O(n)

Advantages of Binary Tree


• Efficient Search: Binary Search Trees (a variation of Binary
Tree) are efficient when searching for a specific element, as
each node has at most two child nodes when compared to
linked list and arrays
• Memory Efficient: Binary trees require lesser memory as
compared to other tree data structures; therefore, they are
memory-efficient.
• Binary trees are relatively easy to implement and understand
as each node has at most two children, left child and right
child.
Disadvantages of Binary Tree
• Limited structure: Binary trees are limited to two child
nodes per node, which can limit their usefulness in certain
applications. For example, if a tree requires more than two
child nodes per node, a different tree structure may be more
suitable.
• Unbalanced trees: Unbalanced binary trees, where one
subtree is significantly larger than the other, can lead to
inefficient search operations. This can occur if the tree is not
properly balanced or if data is inserted in a non-random order.
• Space inefficiency: Binary trees can be space inefficient
when compared to other data structures. This is because each
node requires two child pointers, which can be a significant
amount of memory overhead for large trees.
• Slow performance in worst-case scenarios: In the
worst-case scenario, a binary tree can become degenerate or
skewed, meaning that each node has only one child. In this
case, search operations can degrade to O(n) time complexity,
where n is the number of nodes in the tree.
Applications of Binary Tree
• Binary Tree can be used to represent hierarchical data.
• Huffman Coding trees are used in data compression
algorithms.
• Priority Queue is another application of binary tree that is
used for searching maximum or minimum in O (1) time
complexity.
• Useful for indexing segmented at the database is useful in
storing cache in the system,
• Binary trees can be used to implement decision trees, a type of
machine learning algorithm used for classification and
regression analysis.

Expression Tree
The expression tree is a binary tree in which each internal node
corresponds to the operator and each leaf node corresponds to the
operand so for example expression tree for 3 + ((5+9) *2) would be:

Inorder traversal of expression tree produces infix version of given


postfix expression (same with postorder traversal it gives postfix
expression)

Evaluating the expression represented by an expression


tree:
Let t be the expression tree

If t is not null then

If t.value is operand then

Return t.value

A = solve(t.left)

B = solve(t.right)

Return calculate(A, B, t.value)

Construction of Expression Tree:


Now for constructing an expression tree we use a stack. We loop
through input expression and do the following for every character.

1. If a character is an operand push that into the stack


2. If a character is an operator pop two values from the stack
make them its child and push the current node again.
In the end, the only element of the stack will be the root of an
expression tree.

Examples:
Input: A B C*+ D/

Output: A + B * C / D

The first three symbols are operands, so create tree nodes and push
pointers to them onto a stack as shown below.

In the Next step, an operator ‘*’ will going read, so two pointers to trees
are popped, a new tree is formed and a pointer to it is pushed onto the
stack
In the Next step, an operator ‘+’ will read, so two pointers to trees are
popped, a new tree is formed and a pointer to it is pushed onto the
stack.
Similarly, as above cases first we push ‘D’ into the stack and then in the
last step first, will read ‘/’ and then as previous step topmost element
will pop out and then will be right subtree of root ‘/’ and other nodes will
be right subtree.
Example:
class node {
public:
char value;
node* left;
node* right;
node* next = NULL;
node(char c)
{
this->value = c;
left = NULL;
right = NULL;
}
node()
{
left = NULL;
right = NULL;
}
friend class Stack;
friend class expression_tree;
};
class Stack {
node* head = NULL;

public:
void push(node*);
node* pop();
friend class expression_tree;
};
class expression_tree {
public:
void inorder(node* x)
{
// cout<<"Tree in InOrder Traversal is: "<<endl;
if (x == NULL)
return;
else {
inorder(x->left);
cout << x->value << " ";
inorder(x->right);
}
}
};

void Stack::push(node* x)
{
if (head == NULL) {
head = x;
}
else {
x->next = head;
head = x;
}
}
node* Stack::pop()
{
node* p = head;
head = head->next;
return p;
}
int main()
{
string s = "ABC*+D/";
// If you wish take input from user:
//cout << "Insert Postorder Expression: " << endl;
//cin >> s;
Stack e;
expression_tree a;
node *x, *y, *z;
int l = s.length();
for (int i = 0; i < l; i++) {
if (s[i] == '+' || s[i] == '-' || s[i] == '*'
|| s[i] == '/' || s[i] == '^') {
z = new node(s[i]);
x = e.pop();
y = e.pop();
z->left = y;
z->right = x;
e.push(z);
}
else {
z = new node(s[i]);
e.push(z);
}
}
cout << " The Inorder Traversal of Expression Tree: ";
a.inorder(z);
return 0;
}

Output
The Inorder Traversal of Expression Tree: A + B * C / D

Time complexity: O(n)


Auxiliary space: O(n)
Evaluation of Expression Tree
Algorithm:
• Let t be the syntax tree
• If t is not null then
o If t.info is operand then
o Return t.info
o Else
o A = solve(t.left)
o B = solve(t.right)
o return A operator B, where
operator is the info contained in t
Examples:
Input: Root node of the below tree

Output:100
Applications of trees:
1. Store hierarchical data, like folder structure, organization
structure, XML/HTML data.
2. Binary Search Tree is a tree that allows fast search, insert,
delete on a sorted data. It also allows finding closest item
3. Heap is a tree data structure which is implemented using
arrays and used to implement priority queues.
4. B-Tree and B+ Tree : They are used to implement indexing
in databases.
5. Syntax Tree: Scanning, parsing, generation of code and
evaluation of arithmetic expressions in Compiler design.
6. K-D Tree: A space partitioning tree used to organize points in
K dimensional space.
7. Trie : Used to implement dictionaries with prefix lookup.
8. Suffix Tree : For quick pattern searching in a fixed text.
9. Spanning Trees and shortest path trees are used in routers
and bridges respectively in computer networks

Binary Search Tree


Binary Search Tree is a data structure used in computer science for
organizing and storing data in a sorted manner. Binary search tree
follows all properties of binary tree and its left child contains values
less than the parent node and the right child contains values greater
than the parent node. This hierarchical structure allows for
efficient Searching, Insertion, and Deletion operations on the data
stored in the tree.

What is Binary Search Tree?


Binary Search Tree (BST) is a special type of binary tree in which the
left child of a node has a value less than the node’s value and the right
child has a value greater than the node’s value. This property is called
the BST property and it makes it possible to efficiently search, insert,
and delete elements in the tree.

Properties of Binary Search Tree:


• The left subtree of a node contains only nodes with keys lesser
than 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 (BST may have duplicate
values with different handling approaches).
Basic Operations on Binary Search Tree:
1. Searching a node in BST:
Searching in BST means to locate a specific node in the data structure.
In Binary search tree, searching a node is easy because of its a specific
order. The steps of searching a node in Binary Search tree are listed as
follows –
1. First, compare the element to be searched with the root
element of the tree.
• If root is matched with the target element, then return
the node’s location.
• If it is not matched, then check whether the item is
less than the root element, if it is smaller than the root
element, then move to the left subtree.
• If it is larger than the root element, then move to the
right subtree.
2. Repeat the above procedure recursively until the match is
found.
3. If the element is not found or not present in the tree, then
return NULL.
2. Insert a node into a BST:
A new key is always inserted at the leaf. Start searching a key from the
root till a leaf node. Once a leaf node is found, the new node is added
as a child of the leaf node.
3. Delete a Node of BST:
It is used to delete a node with specific key from the BST and return the
new BST.
Different scenarios for deleting the node:
Node to be deleted is the leaf node:
It’s simple you can just null it out.
Node to be deleted has one child:
You can just replace the node with the child node.

Node to be deleted has two children:


Here we have to delete the node is such a way, that the resulting
tree follows the properties of a BST. The trick is to find the inorder
successor of the node. Copy contents of the inorder successor to the
node, and delete the inorder successor.
Take Care of following things while deleting a node of a
BST:
1. Need to figure out what will be the replacement of the node to
be deleted.
2. Want minimal disruption to the existing tree structure
3. Can take the replacement node from the deleted nodes left or
right subtree.
4. If taking if from the left subtree, we have to take the largest
value in the left subtree.
5. If taking if from the right subtree, we have to take the smallest
value in the right subtree.
4.Traversal (Inorder traversal of BST) :
In case of binary search trees (BST), Inorder traversal gives nodes in
non-decreasing order. We visit the left child first, then the root, and then
the right child.

Applications of BST:
• Self-balancing binary search tree: Self-balancing data
structures such as AVL tree and Red-black tree are the most
useful variations of BSTs. In these variations, we maintain the
height as O(Log n) so that all operations are bounded by
O(Log n). TreeSet and TreeMap in Java (or set and map in
C++) are library implementations of self balancing BSTs.
• Sorted Stream of Data: If we wish to maintain a sorted
stream of data where we wish to have operations like insert,
search, delete and traversal in sorted order, BST is the most
suitable data structure for this case.
• Doubly Ended Priority Queues: With Self Balancing
BSTs, we can extract both maximum and minimum in O (Log
n) time, so when we need a data structure with both operations
supported efficiently, we use self balancing BSTs.
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.
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: For only search, insert and / or delete operations
only hashing is always preferred over BSTs. However, if we
need to maintain sorted data along with these operations, we
use BST.

Threaded Binary Tree


A threaded binary tree is a type of binary tree data structure where the
empty left and right child pointers in a binary tree are replaced with
threads that link nodes directly to their in-order predecessor or
successor, thereby providing a way to traverse the tree without using
recursion or a stack.
Threaded binary trees can be useful when space is a concern, as they
can eliminate the need for a stack during traversal. However, they can
be more complex to implement than standard binary trees.

There are two types of threaded binary trees.


Single Threaded: Where a NULL right pointer is made to point to
the inorder successor (if successor exists)
Double Threaded: Where both left and right NULL pointers are
made to point to inorder predecessor and inorder successor
respectively. The predecessor threads are useful for reverse inorder
traversal and postorder traversal.

The threads are also useful for fast accessing ancestors of a node.
Following diagram shows an example Single Threaded Binary Tree.
The dotted lines represent threads.
struct node {
int data;
struct node* left;
struct node* right;
bool rightThread;
};
or
class Node {
public:
int data;
Node* left;
Node* right;
bool rightThread;
Node(int val){
data = val;
left = NULL;
right = NULL;
rightThread = false;
}
};
Advantages of Threaded Binary Tree
• In this Tree it enables linear traversal of elements.
• It eliminates the use of stack as it performs linear traversal, so
save memory.
• Enables to find parent node without explicit use of parent
pointer
• Threaded tree gives forward and backward traversal of nodes
by in-order fashion
• Nodes contain pointers to in-order predecessor and successor
Disadvantages of Threaded Binary Tree
• Every node in threaded binary tree needs extra information
(extra memory) to indicate whether its left or right node
indicated its child nodes or its inorder predecessor or
successor. So, the node consumes extra memory to
implement.
• Insertion and deletion are way more complex and time
consuming than the normal one since both threads and
ordinary links need to be maintained.
• Implementing threads for every possible node is complicated.
• Increased complexity: Implementing a threaded binary tree
requires more complex algorithms and data structures than a
regular binary tree. This can make the code harder to read and
debug.
• Extra memory usage: In some cases, the additional pointers
used to thread the tree can use up more memory than a
regular binary tree. This is especially true if the tree is not fully
balanced, as threading a skewed tree can result in a large
number of additional pointers.
Applications of threaded binary tree
• Expression evaluation: Threaded binary trees can be
used to evaluate arithmetic expressions in a way that avoids
recursion or a stack. The tree can be constructed from the
input expression, and then traversed in-order or pre-order to
perform the evaluation.
• Database indexing: In a database, threaded binary trees
can be used to index data based on a specific field (e.g. last
name). The tree can be constructed with the indexed values as
keys, and then traversed in-order to retrieve the data in sorted
order.
• Symbol table management: In a compiler or interpreter,
threaded binary trees can be used to store and manage
symbol tables for variables and functions. The tree can be
constructed with the symbols as keys, and then traversed in-
order or pre-order to perform various operations on the symbol
table.
• Disk-based data structures: Threaded binary trees can
be used in disk-based data structures (e.g. B-trees) to improve
performance. By threading the tree, it can be traversed in a
way that minimizes disk seeks and improves locality of
reference.
• Navigation of hierarchical data: In certain applications,
threaded binary trees can be used to navigate hierarchical
data structures, such as file systems or web site directories.
The tree can be constructed from the hierarchical data, and
then traversed in-order or pre-order to efficiently access the
data in a specific order.
AVL Tree Data Structure
An AVL tree defined as a self-balancing Binary Search Tree (BST)
where the difference between heights of left and right subtrees for any
node cannot be more than one.
The difference between the heights of the left subtree and the right
subtree for any node is known as the balance factor of the node.
The AVL tree is named after its inventors, Georgy Adelson-Velsky and
Evgenii Landis, who published it in their 1962 paper “An algorithm for
the organization of information”.
Example of AVL Trees:

The above tree is AVL because the differences between the heights of
left and right subtrees for every node are less than or equal to 1.
Operations on an AVL Tree:
• Insertion
• Deletion
• Searching
Rotating the subtrees in an AVL Tree:
An AVL tree may rotate in one of the following four ways to keep itself
balanced:

Left Rotation:
When a node is added into the right subtree of the right subtree, if the
tree gets out of balance, we do a single left rotation.

Right Rotation:
If a node is added to the left subtree of the left subtree, the AVL tree
may get out of balance, we do a single right rotation.
Left-Right Rotation:
A left-right rotation is a combination in which first left rotation takes
place after that right rotation executes.

Right-Left Rotation:
A right-left rotation is a combination in which first right rotation takes
place after that left rotation executes.
Advantages of AVL Tree:
1. AVL trees can self-balance themselves and therefore provides
time complexity as O (Log n) for search, insert and delete.
2. It is a BST only (with balancing), so items can be traversed in
sorted order.
3. Since the balancing rules are strict compared to Red Black
Tree, AVL trees in general have relatively less height and
hence the search is faster.
4. AVL tree is relatively less complex to understand and
implement compared to Red Black Trees.

Disadvantages of AVL Tree:


1. It is difficult to implement compared to normal BST and easier
compared to Red Black
2. Less used compared to Red-Black trees.
3. Due to its rather strict balance, AVL trees provide complicated
insertion and removal operations as more rotations are
performed.
Applications of AVL Tree:
1. AVL Tree is used as a first example self balancing BST in
teaching DSA as it is easier to understand and implement
compared to Red Black
2. Applications, where insertions and deletions are less common
but frequent data lookups along with other operations of BST
like sorted traversal, floor, ceil, min and max.
3. Red Black tree is more commonly implemented in language
libraries like map in C++, set in C++, TreeMap in
Java and TreeSet in Java.
4. AVL Trees can be used in a real time environment where
predictable and consistent performance is required.

B-Tree
Meet the B-Tree, the multi-talented data structure that can handle
massive amounts of data with ease. When it comes to storing and
searching large amounts of data, traditional binary search trees can
become impractical due to their poor performance and high memory
usage. B-Trees, also known as B-Tree or Balanced Tree, are a type of
self-balancing tree that was specifically designed to overcome these
limitations.

Unlike traditional binary search trees, B-Trees are characterized by the


large number of keys that they can store in a single node, which is why
they are also known as “large key” trees. Each node in a B-Tree can
contain multiple keys, which allows the tree to have a larger branching
factor and thus a shallower height. This shallow height leads to less
disk I/O, which results in faster search and insertion operations. B-
Trees are particularly well suited for storage systems that have slow,
bulky data access such as hard drives, flash memory, and CD-ROMs.
B-Trees maintains balance by ensuring that each node has a minimum
number of keys, so the tree is always balanced. This balance
guarantees that the time complexity for operations such as insertion,
deletion, and searching is always O (log n), regardless of the initial
shape of the tree.

Time Complexity of B-Tree:

Sr. No. Algorithm Time Complexity

1. Search O(log n)

2. Insert O(log n)

3. Delete O(log n)

Note: “n” is the total number of elements in the B-tree

Properties of B-Tree:
• All leaves are at the same level.
• B-Tree is defined by the term minimum degree ‘t ‘. The value
of ‘t ‘depends upon disk block size.
• Every node except the root must contain at least t-1 keys. The
root may contain a minimum of 1 key.
• All nodes (including root) may contain at most (2*t – 1) keys.
• Number of children of a node is equal to the number of keys in
it plus 1.
• All keys of a node are sorted in increasing order. The child
between two keys k1 and k2 contains all keys in the range
from k1 and k2.
• B-Tree grows and shrinks from the root which is unlike Binary
Search Tree. Binary Search Trees grow downward and also
shrink from downward.
• Like other balanced Binary Search Trees, the time complexity
to search, insert, and delete is O (log n).
• Insertion of a Node in B-Tree happens only at Leaf Node.
Following is an example of a B-Tree of minimum order 5

Traversal in B-Tree:
Traversal is also similar to Inorder traversal of Binary Tree. We start
from the leftmost child, recursively print the leftmost child, then repeat
the same process for the remaining children and keys. In the end,
recursively print the rightmost child.
Search Operation in B-Tree:
Search is similar to the search in Binary Search Tree. Let the key to be
searched is k.
• Start from the root and recursively traverse down.
• For every visited non-leaf node,
o If the node has the key, we simply return the
node.
o Otherwise, we recur down to the appropriate
child (The child which is just before the first
greater key) of the node.
• If we reach a leaf node and don’t find k in the leaf node, then
return NULL.
Searching a B-Tree is similar to searching a binary tree. The algorithm
is similar and goes with recursion. At each level, the search is
optimized as if the key value is not present in the range of the parent,
then the key is present in another branch. As these values limit the
search they are also known as limiting values or separation values. If
we reach a leaf node and don’t find the desired key then it will display
NULL.
Algorithm for Searching an Element in a B-Tree: -
struct Node {
int n;
int key[MAX_KEYS];
Node* child[MAX_CHILDREN];
bool leaf;
};
Node* BtreeSearch(Node* x, int k) {
int i = 0;
while (i < x->n && k > x->key[i]) {
i++;
}
if (i < x->n && k == x->key[i]) {
return x;
}
if (x->leaf) {
return nullptr;
}
return BtreeSearch(x->child[i], k);
}

Examples:
Input: Search 120 in the given B-Tree.

Solution:
In this example, we can see that our search was reduced by just
limiting the chances where the key containing the value could be
present. Similarly, if within the above example we’ve to look for 180,
then the control will stop at step 2 because the program will find that
the key 180 is present within the current node. And similarly, if it’s to
seek out 90 then as 90 < 100 so it’ll go to the left subtree automatically,
and therefore the control flow will go similarly as shown within the
above example.

Program:
class BTreeNode {
int* keys;
int t;
BTreeNode** C;
int n;
bool leaf;
public:
BTreeNode(int _t, bool _leaf);
void traverse();
BTreeNode*
search(int k);
friend class BTree;
};
class BTree {
BTreeNode* root;
int t;
public:
BTree(int _t)
{
root = NULL;
t = _t;
}
void traverse()
{
if (root != NULL)
root->traverse();
}
BTreeNode* search(int k)
{
return (root == NULL) ? NULL : root->search(k);
}
};
BTreeNode::BTreeNode(int _t, bool _leaf)
{
t = _t;
leaf = _leaf;
keys = new int[2 * t - 1];
C = new BTreeNode*[2 * t];
n = 0;
}
void BTreeNode::traverse()
{
int i;
for (i = 0; i < n; i++) {
if (leaf == false)
C[i]->traverse();
cout << " " << keys[i];
}
if (leaf == false)
C[i]->traverse();
}
BTreeNode* BTreeNode::search(int k)
{
int i = 0;
while (i < n && k > keys[i])
i++;
if (keys[i] == k)
return this;
if (leaf == true)
return NULL;
return C[i]->search(k);
}

There are two conventions to define a B-Tree, one is to define by


minimum degree, second is to define by order. We have followed the
minimum degree convention and will be following the same in coming
posts on B-Tree. The variable names used in the above program are
also kept the same
Applications of B-Trees:
• It is used in large databases to access data stored on the disk
• Searching for data in a data set can be achieved in
significantly less time using the B-Tree
• With the indexing feature, multilevel indexing can be achieved.
• Most of the servers also use the B-tree approach.
• B-Trees are used in CAD systems to organize and search
geometric data.
• B-Trees are also used in other areas such as natural language
processing, computer networks, and cryptography.
Advantages of B-Trees:
• B-Trees have a guaranteed time complexity of O (log n) for
basic operations like insertion, deletion, and searching, which
makes them suitable for large data sets and real-time
applications.
• B-Trees are self-balancing.
• High-concurrency and high-throughput.
• Efficient storage utilization.
Disadvantages of B-Trees:
• B-Trees are based on disk-based data structures and can
have a high disk usage.
• Not the best for all cases.
• Slow in comparison to other data structures.

B+ Tree
B + Tree is a variation of the B-tree data structure. In a B + tree, data
pointers are stored only at the leaf nodes of the tree. In a B+ tree
structure of a leaf node differs from the structure of internal nodes. The
leaf nodes have an entry for every value of the search field, along with a
data pointer to the record (or to the block that contains this record). The
leaf nodes of the B+ tree is linked together to provide ordered access to
the search field to the records. Internal nodes of a B+ tree are used to
guide the search. Some search field values from the leaf nodes are
repeated in the internal nodes of the B+ tree.
Features of B+ Trees
• Balanced: B+ Trees are self-balancing, which means that as
data is added or removed from the tree, it automatically adjusts
itself to maintain a balanced structure. This ensures that the
search time remains relatively constant, regardless of the size
of the tree.
• Multi-level: B+ Trees are multi-level data structures, with a
root node at the top and one or more levels of internal nodes
below it. The leaf nodes at the bottom level contain the actual
data.
• Ordered: B+ Trees maintain the order of the keys in the tree,
which makes it easy to perform range queries and other
operations that require sorted data.
• Fan-out: B+ Trees have a high fan-out, which means that
each node can have many child nodes. This reduces the
height of the tree and increases the efficiency of searching and
indexing operations.
• Cache-friendly: B+ Trees are designed to be cache-friendly,
which means that they can take advantage of the caching
mechanisms in modern computer architectures to improve
performance.
• Disk-oriented: B+ Trees are often used for disk-based
storage systems because they are efficient at storing and
retrieving data from disk.

Why Use B+ Tree?


• B+ Trees are the best choice for storage systems with sluggish
data access because they minimize I/O operations while
facilitating efficient disc access.
• B+ Trees are a good choice for database systems and
applications needing quick data retrieval because of their
balanced structure, which guarantees predictable performance
for a variety of activities and facilitates effective range-based
queries.

Difference Between B+ Tree and B Tree


Some differences between B+ Tree and B Tree are stated below.
Parameters B+ Tree B Tree

Separate leaf nodes for


Nodes store both keys
Structure data storage and internal
and data values
nodes for indexing

Leaf nodes form a linked


Leaf nodes do not form a
Leaf Nodes list for efficient range-
linked list
based queries

Order Higher order (more keys) Lower order (fewer keys)

Key Typically allows key Usually does not allow


Duplication duplication in leaf nodes key duplication

Better disk access due to More disk I/O due to non-


Disk Access sequential reads in a sequential reads in
linked list structure internal nodes

Database systems, file In-memory data


Applications systems, where range structures, databases,
queries are common general-purpose use

Better performance for Balanced performance


Performance range queries and bulk for search, insert, and
data retrieval delete operations
Parameters B+ Tree B Tree

Requires less memory as


Memory Requires more memory
keys and values are
Usage for internal nodes
stored in the same node

Implementation of B+ Tree
In order, to implement dynamic multilevel indexing, B-tree and B+ tree is
generally employed. The drawback of the B-tree used for indexing,
however, is that it stores the data pointer (a pointer to the disk file block
containing the key value), corresponding to a particular key value, along
with that key value in the node of a B-tree. This technique greatly reduces
the number of entries that can be packed into a node of a B-tree, thereby
contributing to the increase in the number of levels in the B-tree, hence
increasing the search time of a record. B+ tree eliminates the above
drawback by storing data pointers only at the leaf nodes of the tree. Thus,
the structure of the leaf nodes of a B+ tree is quite different from the
structure of the internal nodes of the B tree. It may be noted here that,
since data pointers are present only at the leaf nodes, the leaf nodes
must necessarily store all the key values along with their corresponding
data pointers to the disk file block, in order to access them.
Moreover, the leaf nodes are linked to providing ordered access to the
records. The leaf nodes, therefore form the first level of the index, with
the internal nodes forming the other levels of a multilevel index. Some of
the key values of the leaf nodes also appear in the internal nodes, to
simply act as a medium to control the searching of a record. From the
above discussion, it is apparent that a B+ tree, unlike a B-tree, has two
orders, ‘a’ and ‘b’, one for the internal nodes and the other for the external
(or leaf) nodes.

Structure of B+ Trees

B+ Trees contain two types of nodes:


• Internal Nodes: Internal Nodes are the nodes that are
present in at least n/2 record pointers, but not in the root node,
• Leaf Nodes: Leaf Nodes are the nodes that have n pointers.

Searching a Record in B+ Trees

Let us suppose we have to find 58 in the B+ Tree. We will start by fetching


from the root node then we will move to the leaf node, which might
contain a record of 58. In the image given above, we will get 58 between
50 and 70. Therefore, we will we are getting a leaf node in the third leaf
node and get 58 there. If we are unable to find that node, we will return
that ‘record not founded’ message.

Insertion in B+ Trees
Insertion in B+ Trees is done via the following steps.
• Every element in the tree has to be inserted into a leaf node.
Therefore, it is necessary to go to a proper leaf node.
• Insert the key into the leaf node in increasing order if there is
no overflow.

Deletion in B+Trees
Deletion in B+ Trees is just not deletion but it is a combined process of
Searching, Deletion, and Balancing. In the last step of the Deletion
Process, it is mandatory to balance the B+ Trees, otherwise, it fails in the
property of B+ Trees.

Advantages of B+Trees
• A B+ tree with ‘l’ levels can store more entries in its internal
nodes compared to a B-tree having the same ‘l’ levels. This
accentuates the significant improvement made to the search
time for any given key. Having lesser levels and the presence
of Pnext pointers imply that the B+ trees is very quick and
efficient in accessing records from disks.
• Data stored in a B+ tree can be accessed both sequentially
and directly.
• It takes an equal number of disk accesses to fetch records.
• B+trees have redundant search keys, and storing search keys
repeatedly is not possible.

Disadvantages of B+ Trees
• The major drawback of B-tree is the difficulty of traversing the
keys sequentially. The B+ tree retains the rapid random-access
property of the B-tree while also allowing rapid sequential
access.

Application of B+ Trees
• Multilevel Indexing
• Faster operations on the tree (insertion, deletion, search)
• Database indexing

Heap Data Structure


A Heap is a complete binary tree data structure that satisfies the heap
property: for every node, the value of its children is greater than or
equal to its own value. Heaps are usually used to implement priority
queues, where the smallest (or largest) element is always at the root of
the tree.

What is Heap Data Structure?


A heap is a binary tree-based data structure that follows the heap
property. In a heap, the value of each node is compared to the values
of its children in a specific way:
Types of heaps:
Generally, heaps are of two types.
Max-Heap:
In this heap, the value of the root node must be the greatest among all
its child nodes and the same thing must be done for its left and right
sub-tree also.

The total number of comparisons required in the max heap is according


to the height of the tree. The height of the complete binary tree is
always log n; therefore, the time complexity would also be O(logn).

Min-Heap:
In this heap, the value of the root node must be the smallest among all
its child nodes and the same thing must be done for its left and right
sub-tree also.

The total number of comparisons required in the min heap is according


to the height of the tree. The height of the complete binary tree is
always logn; therefore, the time complexity would also be O(logn).

Properties of Heap:
Heap has the following Properties:
• Complete Binary Tree: A heap tree is a complete binary
tree, meaning all levels of the tree are fully filled except
possibly the last level, which is filled from left to right. This
property ensures that the tree is efficiently represented using
an array.
• Heap Property: This property ensures that the minimum (or
maximum) element is always at the root of the tree according
to the heap type.
• Parent-Child Relationship: The relationship between a
parent node at index ‘i’ and its children is given by the
formulas: left child at index 2i+1 and right child at
index 2i+2 for 0-based indexing of node numbers.
• Efficient Insertion and Removal: Insertion and removal
operations in heap trees are efficient. New elements are
inserted at the next available position in the bottom-rightmost
level, and the heap property is restored by comparing the
element with its parent and swapping if necessary. Removal of
the root element involves replacing it with the last element and
heapifying down.
• Efficient Access to Extremal Elements: The minimum
or maximum element is always at the root of the heap, allowing
constant-time access.

Operations Supported by Heap:


Operations supported by min – heap and max – heap are same. The
difference is just that min-heap contains minimum element at root of the
tree and max – heap contains maximum element at the root of the tree.

Heapify:
It is the process to rearrange the elements to maintain the property of
heap data structure. It is done when a certain node creates an
imbalance in the heap due to some operations on that node. It
takes O(log N) to balance the tree.
• For max-heap, it balances in such a way that the maximum
element is the root of that binary tree and
• For min-heap, it balances in such a way that the minimum
element is the root of that binary tree.
Insertion:

• If we insert a new element into the heap since we are adding a


new element into the heap so it will distort the properties of the
heap so we need to perform the heapify operation so that it
maintains the property of the heap.

This operation also takes O(logN) time.


Examples:
Assume initially heap (taking max-heap) is as follows
8
/ \
4 5
/\
1 2

Now if we insert 10 into the heap


8
/ \
4 5
/ \ /
1 2 10

After heapify operation final heap will be look like this


10
/ \
4 8
/ \ /
1 2 5
Deletion:

• If we delete the element from the heap it always deletes the


root element of the tree and replaces it with the last element of
the tree.
• Since we delete the root element from the heap it will distort
the properties of the heap so we need to perform heapify
operations so that it maintains the property of the heap.
It takes O(logN) time.
Example:
Assume initially heap (taking max-heap) is as follows
15
/ \
5 7
/ \
2 3

Now if we delete 15 into the heap it will be replaced by leaf node of the
tree for temporary.
3
/ \
5 7
/
2

After heapify operation final heap will be look like this


7
/ \
5 3
/
2

getMax (For max-heap) or getMin (For min-heap):

It finds the maximum element or minimum element for max-


heap and min-heap respectively and as we know minimum and
maximum elements will always be the root node itself for min-heap and
max-heap respectively. It takes O (1) time.

removeMin or removeMax:

This operation returns and deletes the maximum element and minimum
element from the max-heap and min-heap respectively. In short, it
deletes the root element of the heap binary tree.

Applications of Heap Data Structure:


• Priority Queues: Priority queues can be efficiently
implemented using Binary Heap because it supports insert(),
delete() and extractmax(), decreaseKey() operations in O(log
N) time.
• Binomial Heap and Fibonacci Heap are variations of
Binary Heap. These variations perform union also in O(log
N) time which is an O(N) operation in Binary Heap.
• Order statistics: The Heap data structure can be used to
efficiently find the kth smallest (or largest) element in an array.
You can see this gfg article to know more about the kth
smallest or largest element.
Advantages of Heaps:
• Fast access to maximum/minimum element (O(1))
• Efficient Insertion and Deletion operations (O(log n))
Flexible size
• Can be efficiently implemented as an array
• Suitable for real-time applications
Disadvantages of Heaps:
• Not suitable for searching for an element other than
maximum/minimum (O(n) in worst case)
• Extra memory overhead to maintain heap structure
• Slower than other data structures like arrays and linked lists for
non-priority queue operations.
Example:
class MaxHeap {
int* arr;
int maxSize;
int heapSize;
public:
MaxHeap(int maxSize);
void MaxHeapify(int);
int parent(int i)
{
return (i - 1) / 2;
}
int lChild(int i)
{
return (2 * i + 1);
}
int rChild(int i)
{
return (2 * i + 2);
}
int removeMax();
void increaseKey(int i, int newVal);
int getMax()
{
return arr[0];
}
int curSize()
{
return heapSize;
}
void deleteKey(int i);
void insertKey(int x);
};
MaxHeap::MaxHeap(int totSize)
{
heapSize = 0;
maxSize = totSize;
arr = new int[totSize];
}
void MaxHeap::insertKey(int x)
{
if (heapSize == maxSize) {
cout << "\nOverflow: Could not insertKey\n";
return;
}
heapSize++;
int i = heapSize - 1;
arr[i] = x;
while (i != 0 && arr[parent(i)] < arr[i]) {
swap(arr[i], arr[parent(i)]);
i = parent(i);
}
}
void MaxHeap::increaseKey(int i, int newVal)
{
arr[i] = newVal;
while (i != 0 && arr[parent(i)] < arr[i]) {
swap(arr[i], arr[parent(i)]);
i = parent(i);
}
}
int MaxHeap::removeMax()
{
if (heapSize <= 0)
return INT_MIN;
if (heapSize == 1) {
heapSize--;
return arr[0];
}
int root = arr[0];
arr[0] = arr[heapSize - 1];
heapSize--;
MaxHeapify(0);
return root;
}
void MaxHeap::deleteKey(int i)
{
increaseKey(i, INT_MAX);
removeMax();
}
void MaxHeap::MaxHeapify(int i)
{
int l = lChild(i);
int r = rChild(i);
int largest = i;
if (l < heapSize && arr[l] > arr[i])
largest = l;
if (r < heapSize && arr[r] > arr[largest])
largest = r;
if (largest != i) {
swap(arr[i], arr[largest]);
MaxHeapify(largest);
}
}
int main()
{
MaxHeap h(15);
int k, i, n = 6, arr[10];
cout << "Entered 6 keys:- 3, 10, 12, 8, 2, 14 \n";
h.insertKey(3);
h.insertKey(10);
h.insertKey(12);
h.insertKey(8);
h.insertKey(2);
h.insertKey(14);
cout << "The current size of the heap is "
<< h.curSize() << "\n";
cout << "The current maximum element is " << h.getMax()
<< "\n";
h.deleteKey(2);
cout << "The current size of the heap is "
<< h.curSize() << "\n";
h.insertKey(15);
h.insertKey(5);
cout << "The current size of the heap is "
<< h.curSize() << "\n";
cout << "The current maximum element is " << h.getMax()
<< "\n";
return 0;
}
Output
Entered 6 keys:- 3, 10, 12, 8, 2, 14

The current size of the heap is 6

The current maximum element is 14

The current size of the heap is 5

The current size of the heap is 7

The current maximum element is 15

You might also like