0% found this document useful (0 votes)
12 views23 pages

Terminology in Trees

fghfghfghfg

Uploaded by

rajanikanthmeka4
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)
12 views23 pages

Terminology in Trees

fghfghfghfg

Uploaded by

rajanikanthmeka4
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/ 23

VSRGDC

What is a tree data structure?

Tree is a widely-used powerful data structure that is a viable addition to a programmer’s


toolkit. A lot of complex problems can be solved relatively easily if the data is stored in a
tree. It is a nonlinear data structure, compared to arrays, linked lists, stacks and queues
which are linear data structures.

Tree is a non-linear data structure which stores the information naturally in the form
of hierarchy style.

Terminology in Trees
Root

 The topmost node in a tree. The tree originates from this node
 This is the only node in the tree that has no parent.
 There is exactly one root in each tree data structure.
Edge

 The line that connects two nodes is known as an edge.


 If there are n nodes in a tree, there exist n-1 edges.
 Edge is also referred to as ‘ branch ’ in trees.
Child

 A node that is a descendant of some node is called child node.


 All nodes(except root) are child nodes.
VSRGDC
Parent

 Parent node is the immediate predecessor of a node.


 Any node which has one or more children is a parent node.
 A group of nodes with the same parent is called siblings.
Leaf

 A node that does not have any child node is a leaf node.
 Leaf nodes are also known as external or terminal nodes.
 A node with atleast one child node is called internal nodes.

Height

 The height of a node is the maximum number of edges between that node and a
leaf node.
 The height of the tree is the height of the root node.
Level

 Level of a node is basically the height difference between that node and the root
node
 Level of root is 0.
VSRGDC

Trees can have any number of children but the simplest and most common type of tree is
a binary tree.

Ancestor: Any node which precedes a node i.e itself, its parent, or an ancestor of its
parents.

Descendent: Any node which follows a node i.e. itself, its child, or a descendent of its
child.

Ordered Tree: A rooted tree in which an ordering is specified for the children of each
vertex. For example: Binary Search Tree, Heap etc.

Size of a tree: Number of nodes in the tree.

A node in a binary tree can have a maximum of 2 child nodes .

Binary Tree Properties


1. Maximum number of nodes in a tree of height h = 2^h-1
2. Minimum number of nodes in a tree of height h = h (One node in each level)
3. Maximum number of nodes on a level l = 2^l - 1
4. In a Binary Tree with N nodes, minimum possible height or minimum number of
levels = Log2(N+1)
5. In Binary tree where every node has 0 or 2 children, number of leaf nodes is
always one more than nodes with two children.
VSRGDC

Binary Tree Types


There are primarily 5 types of binary trees:-

1. Rooted Binary Tree

Rooted Binary tree is the simplest type of binary tree where each node has either 0, 1 or 2
child nodes.

2. Full Tree

Every node has either 0 or 2 children. No node should have only 1 child node. It is also
known as strictly binary tree.

3. Complete Tree

In a complete tree, all internal nodes have 2 children and all leaf nodes are on the same
level. In other words, all levels in a complete tree are entirely filled to its maximum
capacity. It is also known as perfect binary tree .
VSRGDC

4. Almost Complete Tree

In an almost complete tree, all levels are entirely filled except the last level. The last level
is filled from left to right.

5. Skewed Tree

A skewed tree is one in which all nodes have only one child except the leaf node. In a left-
skewed tree, all child nodes are left child nodes and in a right-skewed tree, all child
nodes are right child nodes.
VSRGDC

Advantages and applications of trees


As mentioned above, tree is a very powerful data structure and has a lot of advantages
and applications. Some of these are:

 It is used to represent structural relationships between its data elements.


 It is an effective tool to represent hierarchies like folder structure, XML data or
organization structure.
 Heap is an almost complete binary tree implemented using arrays.
 There are also variations of trees like Tries, B+ Trees, Suffix Trees, Syntax Tree,
etc. which are designed in a way to provide specific benefits in their applications.
Example, B+ trees are used to implement indexing in databases.
 Binary Search Tree(BST) is the most common variation of binary tree providing
extremely efficient insertion, deletion, searching operations.
 Like Linked Lists and unlike Arrays, Trees don’t have an upper limit on number of
nodes as nodes are linked using pointers.

Representation of Binary Trees


We’ve all learnt what a binary tree is, what it’s properties are and what are its
advantages, but do you know how to represent a binary tree?

Confused? No problem.

You are given a simple binary tree, how do you suppose you’re gonna store it in your
program? This is known as the representation of a binary tree.
VSRGDC
There are basically two primary ways we use to represent binary trees:

1. Linked Representation: The parent nodes contain the address of its children.
2. Sequential Representation: Represent the level-order sequence in an array.

Linked Representation
In linked representation of a binary tree, every node is an object of a class. The class is
supposed to represent a node of a tree and each class has mainly three data members:
the value of the node, the reference to the left child and the reference to the right child

class Node
{
int val
Node left
Node right
}
left holds the reference to the left child of a node and right holds the reference to the
right child of the node. Similarly, the entire tree is constructed.

But what if there is no left child or no right child? Well, if there is no left child, the
reference to the left child will hold the value NULL.

Advantages
VSRGDC
 Difficult to implement, but simple to understand.
 Easy to augment the binary tree, like adding additional data or references.
 Does not waste memory.

Sequential Representation
In sequential representation, an array is used which stores the value of the nodes of each
level from left to right.

The size of this array will be 2^h + 1, where H is the height of the tree. If a node is not
present(known as a hole ), it can be represented by a special character such as ‘-’.

As you can see, whether a node is present or not, memory is allocated for a complete
binary tree of height h. So if there are a lot of holes present in the tree, the sequential
representation might be a bad idea as it takes up a lot of space.

But how do we identify the child or parent node of a specific node in sequential
representation?

Let us consider a node at index i .

Parent of a node: At index i/2

Left child of a node: At index i*2


VSRGDC
Right child of a node: At index i*2+1

Advantages

 Difficult to understand but simple to implement.


 Less prone to bugs during implementation.
 Easy to compress and portable as it’s structure is not dependant on memory
addresses.

How to traverse in a tree?

Suppose you’re given an array filled with N elements. How will you traverse all N
elements?

Why is this even a question, right? You will go from index 0 to index N-1 and thereby,
traverse all elements of the array.

But what if you’re given the root of a tree? Can you traverse that tree? How will you
traverse all elements of that tree?

Introduction to Tree Traversals

There is a clear recursive structure in a tree data structure where we can solve a problem
using the solution of its smaller subproblems. So we need to access three different
elements in the tree:
VSRGDC
 Root Node
 Nodes in the left subtree
 Nodes in the right sub-tree
We can access these three elements in six different ways i.e. there are 6 possible
permutations. These are also called DFS traversal of a tree:

 Pre-order: Root -> Left subtree -> Right subtree


 Reverse Pre-order: Root -> Right subtree -> Left subtree
 In-order: Left subtree -> Root -> Right subtree
 Reverse In-order: Right subtree -> Root -> Left subtree
 Post-Order: Left subtree -> Right subtree -> Root
 Reverse Post-order: Right subtree -> Left subtree -> Root
There are some other types of Non-Recursive traversals in trees that you can practice.
We can use these traversals in the solution of several tree problems:

 BFS Traversal or Level order traversal (Using Queue)


 Iterative Traversals (Using Stack): Pre-order, In-order and Post-order
 Morris Traversal or Threaded Binary tree Traversal
Can you think of some other ways of traversing elements in a tree?

In this blog, we are going to discuss the recursive and Iterative approach of following
three traversals:

1. Preorder(NLR): Traverse the node, then the left child(L) and then the right
child(R) → 1 2 3 ( The preorder traversal of above tree )
2. Inorder(LNR): Traverse the left child(L), the node(N) and then the right
child(R) → 2 1 3
3. Postorder(LRN): Traverse the left child(L), the right child(R) and then the
node(N) → 2 3 1
VSRGDC

1. Preorder Traversal
Preorder Traversal as we read above, traverse the tree in the order: Node, left child and
then the right child.

Recursive Implementation

Tree traversals are naturally recursive in nature so let’s see its recursive implementation
first →

void printPreorder(TreeNode root)


{
if ( root == NULL )
return

print ( root.val )
printPreorder( root.left )
printPreorder( root.right )
}
Simple to understand, right?

Well, let’s dive deeper now. In the recursion, the compiler does the stack maintenance
for us. Let us try do it ourselves now.

Iterative implementation
VSRGDC
Now we need to implement the stack ourselves. We need to perform the push() and
pop() operations.

Solution Steps →

1. Create a stack preorder that will temporarily store the elements like the function
stack in the above recursion.
2. Since priority is given to the root, push the root first to the stack.
3. Now we will run a loop until the stack empties, i.e., there are no more elements
left to process.
Now, what order did we follow in the above recursion? Print node, Recurse left and then
recurse right . We shall follow the same order in iteration too.

4. In each iteration, print the node (i.e. the element at top of the stack) then push the
right child in the stack and then the left child.

★ Why did we push the right child first? So that the left child gets to be on top and is
processed first.

5. Don’t forget to pop the top node after printing it in each iteration.

Pseudo Code

void printPreorder(TreeNode root)


{
Create stack 'preorder' to fit elements of type TreeNode
preorder.push( root )
while ( preorder.empty() != True )
{
TreeNode node = preorder.peek()
preorder.pop()
if ( node == NULL )
continue

print ( node.val )
// Note that right child is pushed first
// so that left child is on top
preorder.push( node.right )
preorder.push( node.left )
}
}
VSRGDC

Inorder Traversal
Inorder Traversal follows the order LNR (left, node, right).

Recursive Implementation

void printInorder(TreeNode root)


{
if ( root == NULL )
return

printInorder( root.left )
print ( root.val )
printInorder( root.right )
}
Can you implement this iteratively now using stacks? Let’s try.

Iterative Implementation

Let’s form a strategy to approach the problem. Okay so first of all, we need to find the
left-most node of our current tree. The leftmost node gets printed first. But what next?
We now need the parent node and then the right sibling of this node. So we shall proceed
in that manner and continue doing so until our stack is empty.

Solution Steps →
VSRGDC
1. Create a stack inorder to fit elements of type TreeNode
2. Run a while loop until there are no more elements to process, i.e. the root is
NULL and the stack is empty
3. In each iteration →

 Keep moving to the left child until it exists and push the elements onto the stack
so that we can process them after we process the leftmost element
 Once we reach the left-most element of current subtree, print it and remove it
from the stack.
 What’s the next priority? Either the right subtree or current node or its parent.
 So we move to its right child if it exists. If it doesn’t the parent node is already in
the stack. Pop it and process it
4. Repeat the process until all elements are processed

Pseudo Code

void printInorder(TreeNode root)


{
Create stack 'inorder' to fit elements of type TreeNode
while ( root != NULL or inorder.empty() != True )
{
// Find the leftmost node
while( root != NULL )
{
inorder.push(root)
root = root.left
}

root = inorder.peek()
inorder.pop()
print( root )
root = root.right
}
}

Postorder Traversal
Postorder Traversal follows the LRN policy (left, right, node)
VSRGDC

Recursive Implementation

void printPostorder(TreeNode root)


{
if ( root == NULL )
return
printPostorder(root.left)
printPostorder(root.right)
print ( root.val )
}
What about the iterative implementation? Can you try it yourself based on what you
learnt in the above two iterative implementations?

Iterative Implementation

Let’s go through it in an organized manner. Let us assume we have a stack that prints
postorder traversal of the tree when its elements are popped.

→ Which element do you suppose should be at the bottom-most position of such a


stack? the root of tree ( node is given the least preference)

→ And which element should be at the top-most position of this stack? the leftmost
element in the tree( left child is given the highest priority)
VSRGDC
So in order to create this stack, we should first push the root, then the right
subtree and then the left subtree so that the left subtree is at the top of the stack.
Same priority must be extended while pushing the individual subtrees.

Okay so now let’s design the solution steps keeping in mind the above priorities.

Solution Steps →

1. Firstly, create a stack st which we will use for storing elements temporarily and
then create another stack postorder which will store elements as mentioned
above.
2. Push the root in st.
3. Run a loop until st is not empty, i.e. until all elements haven’t been processed. In
each iteration,

 Pop the top element of st and push it to postorder . This element is the root of
the current subtree and has lowest priority in postorder and is therefore pushed
first onto the stack.
 As mentioned above, we need the left subtree above right subtree in
the postorder stack, therefore, we first push the left child of the current node
and then the right child so that the right child is on top and pushed
onto postorder stack first.
4. Now the postorder stack is created!

5. Pop and print the top element of the postorder stack.

6. Keep repeating step 5 until the postorder stack is empty.

Pseudo Code

void printPostorder(TreeNode root)


{
Create stack 'st' and 'postorder' to fit TreeNode elements
st.push(root)
while ( st.empty() != True )
{
root = st.peek()
st.pop()
if( root == NULL )
continue
postorder.push(root)
st.push(root.left)
VSRGDC
st.push(root.right)
}
while( postorder.empty() != True )
{
TreeNode temp = postorder.peek()
print ( temp.val )
postorder.pop()
}
}

What are the operations implemented in a tree and what are its applications?

We have studied what are trees and how to traverse them. But is that enough? Since it is
such a strong data structure, it must have a good utility which means various operations
could be performed on it.

But what kind of operations? Insertion , deletion and searching of elements are
among the most basic operations that most data structures support.

But there are several other operations that can be performed on trees like →

 Finding the maximum and the minimum element in the tree


 Finding the sum of all elements in the tree
Let us now dive deep in and see how each of these is implemented in trees. For the
purposes of this blog, we will just consider binary trees during implementation for
simplicity. Also, our primary mode of implementation for the tree here will be using
linked representation.

1. Insertion
Insertion in a tree can be performed in various ways depending on where you need to
insert the new element. We can →

 Insert the new element at the rightmost or leftmost vacant position we find.
 Randomly insert the element in the first position we find vacant while we traverse
a tree. The type of traversal(preorder, postorder, inorder, level order) chosen
here may change the final structure of the tree.
 Make the new element root of the binary tree with the previous root as now its
right child and the left child of the previous root will now become the left child of
this new root.
The possibilities are endless…
VSRGDC
 There are many other ways we can find to insert an element in the tree. Comment
down below more ways that you can insert an element in a binary tree.
Among these three, the first method is relatively most difficult to implement so let's try
to implement that.

Solution Steps

We first need to select the type of traversal that we shall perform on the tree to
determine the vacant place. Let us do this with inorder traversal . Parameters: root,
new_node

1. Check if the root itself is null or not, i.e., if it is an empty tree or not.
2. In the case of an empty tree, just return the new_node as root.
3. Begin traversing the tree in inorder fashion →

 Check if left child exists or not, if it doesn’t, make the left child as new_node and
exit, else proceed inorder with the left child.
 Check if right child exists or not, if it doesn’t, make the right child
as new_node and exit, else proceed inorder with the right child.
4. This way, whenever we get an empty spot, we insert the new_node and break the
process that instant.

Pseudo-Code

TreeNode insert_node_inorder(TreeNode root, TreeNode new_node)


{
if ( root == NULL )
return new_node

if ( root.left == NULL )
root.left = new_node
else
root.left = insert_node_inorder(root.left, new_node)
if ( root.right == NULL )
root.right = new_node
else
root.right = insert_node_inorder(root.right, new_node)

return root
}
VSRGDC

2. Searching
Searching an element in a simple binary tree is quite simple actually. We just check if the
current node’s value matches with the given value or not and then recurse to the left and
right subtrees repeating the same process.

Pseudo-Code

bool search(TreeNode root, int item)


{
if ( root == NULL )
return 0
if ( root.val == item )
return 1

if ( search(root.left, item) == 1 )
return 1
else if ( search(root.right, item) == 1 )
return 1
else
return 0
}

3. Deletion
Deletion of a node in a tree may get a little tricky when you think about what to do with
the left child and the right child of that node. We need to follow a certain convention in
this process, whatever the programmer sees fit.

To keep it simple, we shall just replace the deleted node with a leaf node. But which leaf
node? → Well, you can choose this as you wish too. A simple choice is to find the
leftmost or rightmost node of the subtree with the node to be deleted as root.

Although, if the node to be deleted is itself a leaf node, we won’t need to replace it with
any node, or if it has only one child, we can use replace the node with its child.

Solution Steps

Our purpose is to accept the root of tree and value item and return the root of the
modified tree after deleting the node with value item.

1. Check if the root is NULL, if it is, just return the root itself.
VSRGDC
2. Search for an item in left and right subtree and recurse to them if found.
3. If the item is not found in the left and right subtree, either the value is not in the
tree or root.val == item .
4. We now need to delete the root of the tree. There are three cases possible for this.
5. CASE 1: No Child → Just delete root or deallocate space occupied by it
6. CASE 2: One Child →Replace root by its child
7. CASE 3: Two Children

 Find the leftmost node in the left subtree of root. Let us call it new_root.
 Replace root by new_root .
 Now recurse to the left subtree and delete new_root.
8. Return the root . This is a basic approach and hence searches node by value. It is
assumed that all values in the node are unique. Also, keep in mind that a basic binary
tree has no rules like Binary Search Tree for the ordering of elements so you can
change the convention as per your need.

Pseudo-Code

TreeNode delete_element(TreeNode root, int item)


{
if ( root == NULL )
return root

if ( search(root.left, item) == True )


root.left = delete_element(root.left, item)
else if ( search(root.right, item) == True )
root.right = delete_element(root.right, item)

else if ( root.val == item )


{
// No child exists
if ( root.left == NULL and root.right == NULL )
delete root
// Only one child exists
else if ( root.left == NULL or root.right == NULL )
{
if ( root.left == NULL )
return root.right
else
VSRGDC
return root.left
}
// Both left and right child exists
else
{
TreeNode selected_node = root
while ( selected_node.left != NULL )
selected_node = selected_node.left
root.val = selected_node.val
root.left = delete_element(root.left, selected_node.val)
}
}
return root
}

4. Finding minimum and maximum


Finding minimum and maximum in a tree can be done via a traversal too. For example,
we can just do an inorder traversal of the tree and update the values
of min_val and max_val as we traverse the tree.

Let us understand this by a code

void getMinMax(TreeNode root, int& min_val, int& max_val)


{
if ( root == NULL )
return
getMinMax(root.left, min_val, max_val)
min_val = min(min_val, root.val)
max_val = max(max_val, root.val)
getMinMax(root.right, min_val, max_val)
}
→ Here, min_val and max_val are passed by reference and not by value.

5. Sum of all nodes


This is another operation that can be performed based on traversal only. Can you try this
now based on your understanding from the above codes?

Maybe you will write the code something like this →


VSRGDC
void getSum(TreeNode root, int& sum)
{
if ( root == NULL )
return
getSum(root.left, sum)
sum += root.val
getSum(root.right, sum)
}
Although, in this case, the code can also be written in this manner

int getSum(TreeNode root)


{
if ( root == NULL )
return NULL

int left_sum = getSum(root.left)


int right_sum = getSum(root.right)
return left_sum + root.val + right_sum
}
Both codes are correct and equally efficient. It depends on the programmer to right
either one.

Applications of Tree data structure


A Tree is a very useful data structure with a lot of applications and is used internally in
many technologies around you →

 Chatting with someone or googling something? Using autocomplete? Based


on Tries , a tree.
 Using webpages? Transferring data? HTML/XML Parsers are based on tree
data structure as well.
 So many networking models that we use couldn’t be implemented without
trees
 You can already guess some of its various uses in social networking.
And these are just a few examples of how much tree data structure is used in our daily
lives.

Let us list out a few special types of trees with their specific purposes →

 Binary Search Tree(BST) offers fast insert, search and delete operations on
data
VSRGDC
 Indexing on a database is done using B+trees, B- trees and T-Trees
 Tries: Stores strings in tree format decreasing search time
 Heap: A variation of tree implemented using arrays, used to built priority queues
 Pattern Searching in a fixed text is implemented using Suffix Trees
 K-D Tree (K-dimensional) is used to organize points in K-dimensional space

What are the operations implemented in a tree and what are its applications?

You might also like