Terminology in Trees
Terminology in Trees
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
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.
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
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
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?
Advantages
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?
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:
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 →
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
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
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
root = inorder.peek()
inorder.pop()
print( root )
root = root.right
}
}
Postorder Traversal
Postorder Traversal follows the LRN policy (left, right, node)
VSRGDC
Recursive Implementation
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.
→ 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!
Pseudo Code
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 →
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
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
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
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?