Data Structures - U3
Data Structures - U3
asia
UNIT – III
A tree is a non-empty set one element of which is designated the root of the tree while the remaining
elements are partitioned into non-empty sets each of which is a sub-tree of the root.
A tree T is a set of nodes storing elements such that the nodes have a parent-child relationship that
satisfies the following
• If T is not empty, T has a special tree called the root that has no parent.
• Each node v of T different than the root has a unique parent node w; each node with parent w is a child
of w.
Tree nodes have many useful properties. The depth of a node is the length of the path (or the number of
edges) from the root to that node. The height of a node is the longest path from that node to its leaves.
The height of a tree is the height of the root. A leaf node has no children -- its only path is up to its parent.
Binary Tree:
In a binary tree, each node can have at most two children. A binary tree is either empty or consists
of a node called the root together with two binary trees called the left subtree and the right
subtree.
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
Tree Terminology:
Leaf node
A node with no children is called a leaf (or external node). A node which is not a leaf is called an
internal node.
Path
A sequence of nodes n1, n2, . . ., nk, such that ni is the parent of ni + 1 for i = 1, 2,. . ., k - 1. The length
of a path is 1 less than the number of nodes on the path. Thus there is a path of length zero from a
node to itself.
For the tree shown in figure 7.2.1, the path between A and I is A, B, D, I.
Siblings
For the tree shown in figure 7.2.1, F and G are the siblings of the parent node C and H and I are the
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
B is called a descendent of A.
Subtree
Level
The level of the node refers to its distance from the root. The root of the tree has level O, and the
level of any other node in the tree is one more than the level of its parent. For example, in the
binary tree of Figure 7.2.1 node F is at level 2 and node H is at level 3. The maximum number of
Height
The maximum level in a tree determines its height. The height of a node in a tree is the length of a
longest path from the node to a leaf. The term depth is also used to denote height of the tree. The
Depth
The depth of a node is the number of nodes along the path from the root to that node. For
instance, node ‘C’ in figure 7.2.1 has a depth of 1.
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
The nodes of a binary tree can be numbered in a natural way, level by level, left to right. The nodes
of an complete binary tree can be numbered so that the root is assigned the number 1, a left child
is assigned twice the number assigned its parent, and a right child is assigned one more than twice
If every non-leaf node in a binary tree has nonempty left and right subtrees, the tree is termed a strictly
binary tree. Thus the tree of figure 7.2.3(a) is strictly binary. A strictly binary tree with n leaves always
contains 2n - 1 nodes.
A full binary tree of height h has all its leaves at level h. Alternatively; All non leaf nodes of a full binary tree
have two children, and the leaf nodes have no children.
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
A full binary tree with height h has 2h + 1 - 1 nodes. A full binary tree of height h is a strictly binary tree all of
A binary tree with n nodes is said to be complete if it contains all the first n nodes of the above numbering
scheme. Figure 7.2.4 shows examples of complete and incomplete binary trees.
A complete binary tree of height h looks like a full binary tree down to level h-1, and the level h is filled
from left to right.
Array-based Implementation:
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
Tree Traversals
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. Following are the generally used ways for traversing
trees.
Example Tree
Uses of Inorder
In 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 itraversal s reversed, can be
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
used.
Example: Inorder traversal for the above given figure is 4 2 5 1 3.
Preorder Traversal:
Algorithm Preorder(tree)
Uses of Preorder
Preorder traversal is used to create a copy of the tree. Preorder traversal is also used to get prefix
expression on of an expression tree. Please see http://en.wikipedia.org/wiki/Polish_notation to know why
prefix expressions are useful.
Example: Preorder traversal for the above given figure is 1 2 4 5 3.
Postorder Traversal:
Algorithm Postorder(tree)
Uses of Postorder
Postorder traversal is used to delete the tree. Please see the question for deletion of tree for details.
Postorder traversal is also useful to get the postfix expression of an expression tree. Please
seehttp://en.wikipedia.org/wiki/Reverse_Polish_notation to for the usage of postfix expression.
Example: Postorder traversal for the above given figure is 4 5 2 3 1.
#include <stdio.h>
#include <stdlib.h>
struct node
int data;
};
malloc(sizeof(struct node));
node->data = data;
node->left = NULL;
node->right = NULL;
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
return(node);
if (node == NULL)
return;
printPostorder(node->left);
printPostorder(node->right);
if (node == NULL)
return;
printInorder(node->right);
{
if (node == NULL)
return;
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
printf("%d ", node->data);
printPreorder(node->left);
printPreorder(node->right);
int main()
root->left = newNode(2);
root->right = newNode(3);
root->left->left = newNode(4);
root->left->right = newNode(5);
printPreorder(root);
printInorder(root);
printPostorder(root);
getchar();
return 0;
Creation of Binary Tree from In-Order And Pre (Post) Order Traversals:
/ \
/ \
D B E F C
/ \
/ \
B C
/ \ /
/ \ /
D E F
Algorithm: buildTree()
1) Pick an element from Preorder. Increment a Preorder Index Variable (preIndex in below code) to pick
next element in next recursive call.
2) Create a new tree node tNode with the data as picked element.
3) Find the picked element’s index in Inorder. Let the index be inIndex.
4) Call buildTree for elements before inIndex and make the built tree as left subtree of tNode.
5) Call buildTree for elements after inIndex and make the built tree as right subtree of tNode.
6) return tNode.
#include<stdio.h>
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
#include<stdlib.h>
struct node
char data;
};
of inStrt and inEnd should be 0 and len -1. The function doesn't
struct node* buildTree(char in[], char pre[], int inStrt, int inEnd)
return NULL;
if(inStrt == inEnd)
return tNode;
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
return tNode;
/* UTILITY FUNCTIONS */
int i;
if(arr[i] == value)
return i;
node->data = data;
node->left = NULL;
node->right = NULL;
return(node);
if (node == NULL)
return;
printInorder(node->left);
printInorder(node->right);
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
int main()
printInorder(root);
getchar();
Time Complexity: O(n^2). Worst case occurs when tree is left skewed. Example Preorder and Inorder
traversals for worst case are {A, B, C, D} and {D, C, B, A}.
int data;
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
Node *left, *right;
bool rightThread;
Since right pointer is used for two purposes, the boolean variable rightThread is used to indicate whether
right pointer points to right child or inorder successor. Similarly, we can add leftThread for a double threaded
binary tree.
Inorder Taversal using Threads
Following is C code for inorder traversal in a threaded binary tree.
// Utility function to find leftmost node in atree rooted with n
if (n == NULL)
return NULL;
n = n->left;
return n;
// inorder successor
if (cur->rightThread)
cur = cur->right;
cur = leftmost(cur->right);
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
Priority Queues
int item;
int priority;
insert() operation can be implemented by adding an item at end of array in O(1) time.
getHighestPriority() operation can be implemented by linearly searching the highest priority item in
array. This operation takes O(n) time.
deleteHighestPriority() operation can be implemented by first linearly searching an item, then
removing the item by moving all subsequent items one position back.
We can also use Linked List, time complexity of all operations with linked list remains same as
array. The advantage with linked list is deleteHighestPriority() can be more efficient as we don’t
have to move items.
Using Heaps:
Heap is generally preferred for priority queue implementation because heaps provide better
performance compared arrays or linked list. In a Binary Heap, getHighestPriority() can be
implemented in O(1) time, insert() can be implemented in O(Logn) time and deleteHighestPriority()
can also be implemented in O(Logn) time.
With Fibonacci heap, insert() and getHighestPriority() can be implemented in O(1) amortized time
and deleteHighestPriority() can be implemented in O(Logn) amortized time.
Applications of Priority Queue:
1) CPU Scheduling
2) Graph algorithms like Dijkstra’s shortest path algorithm, Prim’s Minimum Spanning Tree, etc
3) All queue applications where priority is involved.
Heap operations
Both the insert and remove operations modify the heap to conform to the shape property first,
by adding or removing from the end of the heap. Then the heap property is restored by
traversing up or down the heap. Both operations take O(log n) time.
Insert
To add an element to a heap we must perform an up-heap operation (also known as bubble-
up, percolate-up, sift-up, trickle-up, heapify-up, or cascade-up), by following this algorithm:
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
The number of operations required is dependent on the number of levels the new element
must rise to satisfy the heap property, thus the insertion operation has a time complexity of
O(log n). However, in 1974, Thomas Porter and Istvan Simon proved that the function for the
average number of levels an inserted node moves up is upper bounded by the constant
1.6067.[5] The average number of operations required for an insertion into a binary heap is
2.6067 since one additional comparison is made that does not result in the inserted node
moving up a level. Thus, on average, binary heap insertion has a constant, O(1), time
complexity. Intuitively, this makes sense since approximately 50% of the elements are leaves
and approximately 75% of the elements are in the bottom two levels, it is likely that the new
element to be inserted will only move a few levels upwards to maintain the heap.
and we want to add the number 15 to the heap. We first place the 15 in the position
marked by the X. However, the heap property is violated since 15 > 8, so we need to
swap the 15 and the 8. So, we have the heap looking as follows after the first swap:
However the heap property is still violated since 15 > 11, so we need to swap again:
which is a valid max-heap. There is no need to check left child after this final step.
Before we placed 15 on X in 1-st step, the max-heap was valid, meaning 11 > 5. If
15 > 11, and 11 > 5, then 15 > 5, because of the transitive relation.
Extract
The procedure for deleting the root from the heap (effectively extracting the
maximum element in a max-heap or the minimum element in a min-heap) and
restoring the properties is called down-heap (also known as bubble-
down, percolate-down, sift-down, trickle down, heapify-down, cascade-
down, boogie-down, and extract-min/max).
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
1. Replace the root of the heap with the last element on the last level.
2. Compare the new root with its children; if they are in the correct order,
stop.
3. If not, swap the element with one of its children and return to the previous
step. (Swap with its smaller child in a min-heap and its larger child in a
max-heap.)
Now the heap property is violated since 8 is greater than 4. In this case,
swapping the two elements, 4 and 8, is enough to restore the heap
property and we need not swap elements further:
Max-Heapify(A, largest)
For the above algorithm to correctly re-heapify the array, the node at
index i and its two direct children must violate the heap property. If they
do not, the algorithm will fall through with no change to the array. The
down-heap operation (without the preceding swap) can also be used to
modify the value of the root, even when an element is not being
deleted.
In the worst case, the new root has to be swapped with its child on
each level until it reaches the bottom level of the heap, meaning that
the delete operation has a time complexity relative to the height of the
tree, or O(log n).
Heap operations
Building A Heap
Building A Heap To create a heap, we repeatedly insert the new values. Ex Build a heap by inserting the
following values in order:
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
Pros: Representation is easier to implement and follow. Removing an edge takes O(1) time.
Queries like whether there is an edge from vertex ‘u’ to vertex ‘v’ are efficient and can be done
O(1).
Cons: Consumes more space O(V^2). Even if the graph is sparse(contains less number of edges),
it consumes the same space. Adding a vertex is O(V^2) time.
Adjacency List:
An array of linked lists is used. Size of the array is equal to number of vertices. Let the array be
array[]. An entry array[i] represents the linked list of vertices adjacent to the ith vertex. This
representation can also be used to represent a weighted graph. The weights of edges can be
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
stored in nodes of linked lists. Following is adjacency list representation of the above graph.
Following is C++ implementation of simple Breadth First Traversal from a given source. The
implementation usesadjacency list representation of graphs. STL‘s list container is used to store
lists of adjacent nodes and queue of nodes needed for BFS traversal.
// Program to print BFS traversal from a given source vertex. BFS(int s)
#include<iostream>
#include <list>
class Graph
public:
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
void addEdge(int v, int w); // function to add an edge to graph
};
Graph::Graph(int V)
this->V = V;
void Graph::BFS(int s)
visited[i] = false;
list<int> queue;
visited[s] = true;
queue.push_back(s);
list<int>::iterator i;
while(!queue.empty())
s = queue.front();
queue.pop_front();
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
// Get all adjacent vertices of the dequeued vertex s
// and enqueue it
if(!visited[*i])
visited[*i] = true;
queue.push_back(*i);
int main()
Graph g(4);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
cout << "Following is Breadth First Traversal (starting from vertex 2) \n";
g.BFS(2);
return 0;
Output:
2 0 3 1
Note that the above code traverses only the vertices reachable from a given source vertex. All the
vertices may not be reachable from a given vertex (example Disconnected graph). To print all the
vertices, we can modify the BFS function to do traversal starting from all nodes one by one (Like
the DFS modified version) .
Time Complexity: O(V+E) where V is number of vertices in the graph and E is number of edges in
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
the graph.
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
#include <list>
class Graph
public:
};
Graph::Graph(int V)
this->V = V;
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
{
visited[v] = true;
list<int>::iterator i;
if(!visited[*i])
DFSUtil(*i, visited);
void Graph::DFS(int v)
visited[i] = false;
DFSUtil(v, visited);
int main()
Graph g(4);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
cout << "Following is Depth First Traversal (starting from vertex 2) \n";
g.DFS(2);
return 0;
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
}
Output:
2 0 1 3
Note that the above code traverses only the vertices reachable from a given source vertex. All the
vertices may not be reachable from a given vertex (example Disconnected graph). To do complete
DFS traversal of such graphs, we must call DFSUtil() for every vertex. Also, before calling
DFSUtil(), we should check if it is already printed by some other call of DFSUtil(). Following
implementation does the complete graph traversal even if the nodes are unreachable. The
differences from the above code are highlighted in the below code.
#include<iostream>
#include <list>
class Graph
public:
};
Graph::Graph(int V)
this->V = V;
jntuworldupdates.org Specworld.in
Smartzworld.com Smartworld.asia
visited[v] = true;
list<int>::iterator i;
if(!visited[*i])
DFSUtil(*i, visited);
void Graph::DFS()
visited[i] = false;
if(visited[i] == false)
DFSUtil(i, visited);
int main()
Graph g(4);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
cout << "Following is Depth First Traversal (starting from vertex 0) \n";
g.DFS();
jntuworldupdates.org Specworld.in