NOTE_071520
NOTE_071520
In computer science, a data structure is a way of organizing and storing data in a computer
program so that it can be accessed and used efficiently. Data structures provide a means of
managing large amounts of data, enabling efficient searching, sorting, insertion, and deletion
of data.
A data structure is a storage that is used to store and organize data. It is a way of
arranging data on a computer so that it can be accessed and updated efficiently.
A data structure is not only used for organizing the data. It is also used for processing,
retrieving, and storing data. There are different basic and advanced types of data structures
that are used in almost every program or software system that has been developed. So we
must have good knowledge about data structures.
Characteristics of an Algorithm?
As one would not follow any written instructions to cook the recipe, but only the standard
one. Similarly, not all written instructions for programming is an algorithm. In order for some
instructions to be an algorithm, it must have the following characteristics:
1) Clear and Unambiguous: The algorithm should be clear and unambiguous. Each of
its steps should be clear in all aspects and must lead to only one meaning.
2) Well-Defined Inputs: If an algorithm says to take inputs, it should be well-defined
inputs. It may or may not take input.
3) Well-Defined Outputs: The algorithm must clearly define what output will be
yielded and it should be well-defined as well. It should produce at least 1 output.
4) Finite-ness: The algorithm must be finite, i.e. it should terminate after a finite time.
5) Feasible: The algorithm must be simple, generic, and practical, such that it can be
executed with the available resources. It must not contain some future technology or
anything.
6) Language Independent: The Algorithm designed must be language-independent, i.e.
it must be just plain instructions that can be implemented in any language, and yet the
output will be the same, as expected.
7) Input: An algorithm has zero or more inputs. Each that contains a fundamental
operator must accept zero or more inputs.
8) Output: An algorithm produces at least one output. Every instruction that contains a
fundamental operator must accept zero or more inputs.
9) Definiteness: All instructions in an algorithm must be unambiguous, precise, and
easy to interpret. By referring to any of the instructions in an algorithm one can
clearly understand what is to be done. Every fundamental operator in instruction must
be defined without any ambiguity.
10) Effectiveness: An algorithm must be developed by using very basic, simple, and
feasible operations so that one can trace it out by using just paper and pencil.
As we know that all programming languages share basic code constructs like loops (do, for,
while), flow-control (if-else), etc. These common constructs can be used to write an
algorithm.
We write algorithms in a step-by-step manner, but it is not always the case. Algorithm
writing is a process and is executed after the problem domain is well-defined. That is, we
should know the problem domain, for which we are designing a solution.
Example
Let's try to learn algorithm-writing by using an example.
Problem − Design an algorithm to add two numbers and display the result.
Step 1 − START
Step 2 − declare three variables a, b & c
Step 3 − define values of a & b
Step 4 − add values of a & b and assign to c
Step 5 − print c
Step 6 − STOP
Algorithms tell the programmers how to code the program. Alternatively, the algorithm can
be written as −
Step 1 − START ADD
Step 2 − get values of a & b
Step 3 − c = a + b
Step 4 − display c
Step 5 − STOP
In design and analysis of algorithms, usually the second method is used to describe an
algorithm. It makes it easy for the analyst to analyze the algorithm ignoring all unwanted
definitions. He can observe what operations are being used and how the process is flowing.
ALGORITHM COMPLEXITY
Suppose X is an algorithm and n is the size of input data, the time and space used by the
algorithm X are the two main factors, which decide the efficiency of X.
1) Time Factor − Time is measured by counting the number of key operations such as
comparisons in the sorting algorithm.
2) Space Factor − Space is measured by counting the maximum memory space required
by the algorithm.
The complexity of an algorithm f(n) gives the running time and/or the storage space required
by the algorithm in terms of n as the size of input data.
SPACE COMPLEXITY
Space complexity of an algorithm represents the amount of memory space required by the
algorithm in its life cycle. The space required by an algorithm is equal to the sum of the
following two components −
➢ A fixed part that is a space required to store certain data and variables, that are
independent of the size of the problem. For example, simple variables and constants
used, program size, etc.
➢ A variable part is a space required by variables, whose size depends on the size of the
problem. For example, dynamic memory allocation, recursion stack space, etc.
Space complexity S(P) of any algorithm P is S(P) = C + S(I), where C is the fixed part and
S(I) is the variable part of the algorithm, which depends on instance characteristic I.
Following is a simple example that tries to explain the concept −
Step 1 - START
Step 2 - C = A + B + 10
Step 3 - Stop
Here we have three variables A, B, and C and one constant. Hence S(P) = 1 + 3. Now, space
depends on data types of given variables and constant types and it will be multiplied
accordingly.
TIME COMPLEXITY
Time complexity of an algorithm represents the amount of time required by the algorithm to
run to completion. Time requirements can be defined as a numerical function T(n), where
T(n) can be measured as the number of steps, provided each step consumes constant time.
For example, addition of two n-bit integers takes n steps. Consequently, the total
computational time is T(n) = c ∗ n, where c is the time taken for the addition of two bits.
Here, we observe that T(n) grows linearly as the input size increases.
ASYMPTOTIC ANALYSIS
Asymptotic analysis of an algorithm refers to defining the mathematical foundation /framing
of its run-time performance. Using asymptotic analysis, we can very well conclude the best
case, average case, and worst case scenario of an algorithm.
Asymptotic analysis is input bound i.e., if there's no input to the algorithm, it is concluded to
work in a constant time. Other than the "input" all other factors are considered constant.
Asymptotic analysis refers to computing the running time of any operation in mathematical units of
computation. For example, the running time of one operation is computed as f(n) and may be for
another operation it is computed as g(n2). This means the first operation running time will increase
linearly with the increase in n and the running time of the second operation will increase
exponentially when n increases. Similarly, the running time of both operations will be nearly the
same if n is significantly small.
Usually, the time required by an algorithm falls under three types −
ASYMPTOTIC NOTATIONS
Following are the commonly used asymptotic notations to calculate the running time
complexity of an algorithm.
➢ Ο Notation
➢ Ω Notation
➢ θ Notation
BIG OH NOTATION, Ο
The notation Ο(n) is the formal way to express the upper bound of an algorithm's running
time. It measures the worst case time complexity or the longest amount of time an algorithm
can possibly take to complete.
OMEGA NOTATION, Ω
The notation Ω(n) is the formal way to express the lower bound of an algorithm's running
time. It measures the best case time complexity or the best amount of time an algorithm can
possibly take to complete.
THETA NOTATION, Θ
The notation θ(n) is the formal way to express both the lower bound and the upper bound of
an algorithm's running time. It is represented as follows −
Common Asymptotic Notations
Following is a list of some common asymptotic notations –
constant Ο(1)
logarithmic Ο(log n)
linear Ο(n)
quadratic Ο(n2)
cubic Ο(n3)
polynomial nΟ(1)
exponential 2Ο(n)
Greedy algorithms try to find a localized optimum solution, which may eventually lead to
globally optimized solutions. However, generally greedy algorithms do not provide globally
optimized solutions.
Counting Coins
This problem is to count to a desired value by choosing the least possible coins and the
greedy approach forces the algorithm to pick the largest possible coin. If we are provided
coins of 1, 2, 5 and 10 and we are asked to count 18 then the greedy procedure will be −
For the currency system, where we have coins of 1, 7, 10 value, counting coins for value 18
will be absolutely optimum but for count like 15, it may use more coins than necessary. For
example, the greedy approach will use 10 + 1 + 1 + 1 + 1 + 1, total 6 coins. Whereas the
same problem could be solved by using only 3 coins (7 + 7 + 1)
Hence, we may conclude that the greedy approach picks an immediate optimized solution and
may fail where global optimization is a major concern.
Examples
Most networking algorithms use the greedy approach. Here is a list of few of them −
Examples
The following computer algorithms are based on divide-and-conquer programming approach
➢ Merge Sort
➢ Quick Sort
➢ Binary Search
➢ Strassen's Matrix Multiplication
➢ Closest pair (points)
Dynamic programming is used where we have problems, which can be divided into similar
sub-problems, so that their results can be re-used. Mostly, these algorithms are used for
optimization. Before solving the in-hand sub-problem, dynamic algorithm will try to examine
the results of the previously solved sub-problems. The solutions of sub-problems are
combined in order to achieve the best solution.
Comparison
In contrast to greedy algorithms, where local optimization is addressed, dynamic algorithms
are motivated for an overall optimization of the problem.
In contrast to divide and conquer algorithms, where solutions are combined to achieve an
overall solution, dynamic algorithms use the output of a smaller sub-problem and then try to
optimize a bigger sub-problem. Dynamic algorithms use Memorization to remember the
output of already solved sub-problems.
Example
The following computer problems can be solved using dynamic programming approach −
Dynamic programming can be used in both top-down and bottom-up manner. And of course,
most of the times, referring to the previous solution output is cheaper than recomputing in
terms of CPU cycles.
CLASSIFICATION OF ALGORITHMS
Overall, the classification of algorithms plays a crucial role in computer science and helps to
improve the efficiency and effectiveness of solving problems.
B) Classification by Design Method: There are primarily three main categories into which an
algorithm can be named in this type of classification. They are:
1) Greedy Method: In the greedy method, at each step, a decision is made to choose
the local optimum, without thinking about the future consequences.
Example: Fractional Knapsack, Activity Selection.
2) Divide and Conquer: The Divide and Conquer strategy involves dividing the problem
into sub-problem, recursively solving them, and then recombining them for the final answer.
Example: Merge sort, Quicksort.
3) Linear Programming: In Linear Programming, there are inequalities in terms of inputs
and maximizing or minimizing some linear functions of inputs.
Example: Maximum flow of Directed Graph
4) Backtracking: This technique is very useful in solving combinatorial problems that have a
single unique solution. Where we have to find the correct combination of steps that lead to
fulfilment of the task. Such problems have multiple stages and there are multiple options at
each stage. This approach is based on exploring each available option at every stage one-by-
one. While exploring an option if a point is reached that doesn’t seem to lead to the solution,
the program control backtracks one step, and starts exploring the next option. In this way, the
program explores all possible course of actions and finds the route that leads to the solution.
Example: N-queen problem, maize problem.
EXAMPLES OF ALGORITHM
ALGORITHM 1: Add two numbers entered by the user
Step 1: Start
Step 2: Declare variables num1, num2 and sum.
Step 3: Read values num1 and num2.
Step 4: Add num1 and num2 and assign the result to sum.
Sum=num1+num2
Step 5: Display sum
Step 6: Stop
What is Array?
An array is a collection of items stored at contiguous memory locations. The idea is to store
multiple items of the same type together. This makes it easier to calculate the position of each
element by simply adding an offset to a base value, i.e., the memory location of the first
element of the array (generally denoted by the name of the array).
Array Representation
Arrays can be declared in various ways in different languages. For illustration, let's take C
array declaration.
As per the above illustration, following are the important points to be considered.
In C++, when an array is initialized with size, then it assigns defaults values to its
elements in following order.
Traverse Operation
This operation is to traverse through the elements of an array.
Example
Following program traverses and prints the elements of an array:
#include <iostream>
Using namespace std;
Int main() {
int LA[] = {1,3,5,7,8};
int item = 10, k = 3, n = 5;
int i = 0, j = n;
cout<<"The original array elements are:\n"<<endl;
for(i = 0; i<n; i++) {
cout<<"LA[%d] = %d \n"<<i<<LA[i]<<endl;
}
}
Output
The original array elements are:
LA[0] = 1
LA[1] = 3
LA[2] = 5
LA[3] = 7
LA[4] = 8
Insertion Operation
Insert operation is to insert one or more data elements into an array. Based on the
requirement, a new element can be added at the beginning, end, or any given index of array.
Here, we see a practical implementation of insertion operation, where we add data at the end
of the array −
Example
#include <iostream>
Using namespace std;
Int main() {
int LA[] = {1,3,5,7,8};
int item = 10, k = 3, n = 5;
int i = 0, j = n;
while(j>= k) {
LA[j+1] = LA[j];
j = j - 1;
}
LA[k] = item;
When we compile and execute the above program, it produces the following result −
Output
The original array elements are:
LA[0] = 1
LA[1] = 3
LA[2] = 5
LA[3] = 7
LA[4] = 8
The array elements after insertion:
LA[0] = 1
LA[1] = 3
LA[2] = 5
LA[3] = 10
LA[4] = 7
LA[5] = 8
Deletion Operation
Deletion refers to removing an existing element from the array and re-organizing all
elements of an array.
Algorithm
Consider LA is a linear array with N elements and K is a positive integer such
that K<=N. Following is the algorithm to delete an element available at the Kth position
of LA.
1. Start
2. Set J = K
3. Repeat steps 4 and 5 while J < N
4. Set LA[J] = LA[J + 1]
5. Set J = J+1
6. Set N = N-1
7. Stop
Example
Following is the implementation of the above algorithm −
Live Demo
#include <stdio.h>
void main() {
int LA[] = {1,3,5,7,8};
int k = 3, n = 5;
int i, j;
j = k;
while( j < n) {
LA[j-1] = LA[j];
j = j + 1;
}
n = n -1;
A linked list is a collection of “nodes” connected together via links. These nodes consist of
the data to be stored and a pointer to the address of the next node within the linked list. In the
case of arrays, the size is limited to the definition, but in linked lists, there is no defined size.
Any amount of data can be stored in it and can be deleted from it.
➢ Singly Linked List − The nodes only point to the address of the next node in the list.
➢ Doubly Linked List − The nodes point to the addresses of both previous and next
nodes.
➢ Circular Linked List − The last node in the list will point to the first node in the list.
It can either be singly linked or doubly linked.
As per the above illustration, following are the important points to be considered.
1. Linked List contains a link element called first (head).
2. Each link carries a data field(s) and a link field called next.
3. Each link is linked with its next link using its next link.
4. Last link carries a link as null to mark the end of the list.
Types of Linked List
Following are the various types of linked list.
Now, the next node at the left should point to the new node.
LeftNode.next −> NewNode;
This will put the new node in the middle of the two. The new list should look like this −
Insertion in linked list can be done in three different ways. They are explained as follows −
Algorithm
1. START
2. Create a node to store the data
3. Check if the list is empty
4. If the list is empty, add the data to the node and assign the head pointer to it.
5 If the list is not empty, add the data to a node and link to the current head. Assign the head to the newly added
node.
6. END
What is Stack?
A stack is an Abstract Data Type (ADT), that is popularly used in most programming
languages. It is named stack because it has the similar operations as the real-world stacks, for
example – a pack of cards or a pile of plates, etc.
The stack follows the LIFO (Last in - First out) structure where the last element inserted
would be the first element deleted.
A stack can be implemented by means of Array, Structure, Pointer, and Linked List. Stack
can either be a fixed size one or it may have a sense of dynamic resizing. Here, we are going
to implement stack using arrays, which makes it a fixed size stack implementation.
Basic Operations on Stacks
Stack operations usually are performed for initialization, usage and, de-initialization of the
stack ADT.
The most fundamental operations in the stack ADT include: push(), pop(), peek(), isFull(),
isEmpty(). These are all built-in operations to carry out data manipulation and to check the
status of the stack.
Stack uses pointers that always point to the topmost element within the stack, hence called as
the top pointer.
Insertion: push()
push() is an operation that inserts elements into the stack. The following is an algorithm that
describes the push() operation in a simpler way.
Algorithm
1 − Checks if the stack is full.
2 − If the stack is full, produces an error and exit.
3 − If the stack is not full, increments top to point next empty space.
4 − Adds data element to the stack location, where top is pointing.
5 − Returns success.
Deletion: pop()
pop() is a data manipulation operation which removes elements from the stack. The following
pseudo code describes the pop() operation in a simpler way.
Algorithm
1 − Checks if the stack is empty.
2 − If the stack is empty, produces an error and exit.
3 − If the stack is not empty, accesses the data element at which top is pointing.
4 − Decreases the value of top by 1.
5 − Returns success.
peek()
The peek() is an operation retrieves the topmost element within the stack, without deleting it.
This operation is used to check the status of the stack with the help of the top pointer.
Algorithm
1. START
2. return the element at the top of the stack
3. END
isFull()
isFull() operation checks whether the stack is full. This operation is used to check the status
of the stack with the help of top pointer.
Algorithm
1. START
2. If the size of the stack is equal to the top position of the stack, the stack is full. Return 1.
3. Otherwise, return 0.
4. END
isEmpty()
The isEmpty() operation verifies whether the stack is empty. This operation is used to
check the status of the stack with the help of top pointer.
Algorithm
1. START
2. If the top value is -1, the stack is empty. Return 1.
3. Otherwise, return 0.
4. END
DATA STRUCTURE - EXPRESSION PARSING
The way to write arithmetic expression is known as a notation. An arithmetic expression can
be written in three different but equivalent notations, i.e., without changing the essence or
output of an expression. These notations are −
• Infix Notation
• Prefix (Polish) Notation
• Postfix (Reverse-Polish) Notation
These notations are named as how they use operator in expression. We shall learn
the same here in this chapter.
Infix Notation
We write expression in infix notation, e.g. a - b + c, where operators are used in-
between operands. It is easy for us humans to read, write, and speak in infix notation
but the same does not go well with computing devices. An algorithm to process infix
notation could be difficult and costly in terms of time and space consumption.
Prefix Notation
In this notation, operator is prefixed to operands, i.e. operator is written ahead of
operands. For example, +ab. This is equivalent to its infix notation a + b. Prefix
notation is also known as Polish Notation.
Postfix Notation
This notation style is known as Reversed Polish Notation. In this notation style, the
operator is postfixed to the operands i.e., the operator is written after the operands.
For example, ab+. This is equivalent to its infix notation a + b.
The following table briefly tries to show the difference in all three notations −
Parsing Expressions
As we have discussed, it is not a very efficient way to design an algorithm or program to
parse infix notations. Instead, these infix notations are first converted into either postfix or
prefix notations and then computed.
To parse any arithmetic expression, we need to take care of operator precedence and
associativity also.
Precedence
When an operand is in between two different operators, which operator will take the operand
first, is decided by the precedence of an operator over others. For example −
a + b * c => a + (b * c)
The above table shows the default behavior of operators. At any point of time in expression
evaluation, the order can be altered by using parenthesis. For example −
In a + b*c, the expression part b*c will be evaluated first, with multiplication as precedence
over addition. We here use parenthesis for a + b to be evaluated first, like (a + b)*c.
Postfix Evaluation Algorithm
We shall now look at the algorithm on how to evaluate postfix notation −
Step 1 − scan the expression from left to right
Step 2 − if it is an operand push it to stack
Step 3 − if it is an operator pull operand from stack and perform operation
Step 4 − store the output of step 3, back to stack
Step 5 − scan the expression until all operands are consumed
Step 6 − pop the stack and perform operation
A real-world example of queue can be a single-lane one-way road, where the vehicle enters
first, exits first. More real-world examples can be seen as queues at the ticket windows and
bus-stops.
Representation of Queues
Similar to the stack ADT, a queue ADT can also be implemented using arrays, linked lists, or
pointers. As a small example in this tutorial, we implement queues using a one-dimensional
array.
Types of Queues:
There are five different types of queues that are used in different
scenarios. They are:
1. input Restricted Queue (this is a Simple Queue)
2. Output Restricted Queue (this is also a Simple Queue)
3. Circular Queue
4. Double Ended Queue (Deque)
5. Priority Queue
• Ascending Priority Queue
• Descending Priority Queue
Basic Operations
Queue operations also include initialization of a queue, usage and permanently deleting the
data from the memory.
The most fundamental operations in the queue ADT include: enqueue(), dequeue(), peek(),
isFull(), isEmpty(). These are all built-in operations to carry out data manipulation and to
check the status of the queue.
Queue uses two pointers − front and rear. The front pointer accesses the data from the front
end (helping in enqueueing) while the rear pointer accesses data from the rear end (helping in
dequeening).
Insertion operation: enqueue()
The enqueue() is a data manipulation operation that is used to insert elements into the stack.
The following algorithm describes the enqueue() operation in a simpler way.
Algorithm
1 − START
2 – Check if the queue is full.
3 − If the queue is full, produce overflow error and exit.
4 − If the queue is not full, increment rear pointer to point the next empty space.
5 − Add data element to the queue location, where the rear is pointing.
6 − return success.
7 – END
Algorithm
1 – START
2 – Return the element at the front of the queue
3 – END
Important Terms
Following are the important terms with respect to tree.
1. Path − Path refers to the sequence of nodes along the edges of a tree.
2. Root − The node at the top of the tree is called root. There is only one root per tree
and one path from the root node to any node.
3. Parent − Any node except the root node has one edge upward to a node called parent.
4. Child − The node below a given node connected by its edge downward is called its
child node.
5. Leaf − The node which does not have any child node is called the leaf node.
6. Subtree − Subtree represents the descendants of a node.
7. Visiting − Visiting refers to checking the value of a node when control is on the node.
8. Traversing − Traversing means passing through nodes in a specific order.
9. Levels − Level of a node represents the generation of a node. If the root node is at
level 0, then its next child node is at level 1, its grandchild is at level 2, and so on.
10. Keys − Key represents a value of a node based on which a search operation is to be
carried out for a node.
Types of Trees
A) General Trees
B) Binary Trees
C) Binary Search Trees
General Trees
General trees are unordered tree data structures where the root node has minimum 0 or
maximum ‘n’ subtrees.
The General trees have no constraint placed on their hierarchy. The root node thus acts like
the superset of all the other subtrees.
Binary Trees
Binary Trees are general trees in which the root node can only hold up to maximum 2
subtrees: left subtree and right subtree. Based on the number of children, binary trees are
divided into three types.
The data in the Binary Search Trees (BST) is always stored in such a way that the values in
the left subtree are always less than the values in the root node and the values in the right
subtree are always greater than the values in the root node, i.e. left subtree < root node ≤ right
subtree.
Advantages of BST
A) Binary Search Trees are more efficient than Binary Trees since time complexity for
performing various operations reduces.
B) Since the order of keys is based on just the parent node, searching operation becomes
simpler.
C) The alignment of BST also favours Range Queries, which are executed to find values
existing between two keys. This helps in the Database Management System.
Disadvantages of BST
The main disadvantage of Binary Search Trees is that if all elements in nodes are either
greater than or lesser than the root node, the tree becomes skewed. Simply put, the tree
becomes slanted to one side completely.
This skewness will make the tree a linked list rather than a BST, since the worst case time
complexity for searching operation becomes O(n).
To overcome this issue of skewness in the Binary Search Trees, the concept of Balanced
Binary Search Trees was introduced.
A) In-order Traversal
B) Pre-order Traversal
C) Post-order Traversal
Generally, we traverse a tree to search or locate a given item or key in the tree or to print all
the values it contains.
A) In-Order Traversal
In this traversal method, the left subtree is visited first, then the root and later the right sub-
tree. We should always remember that every node may represent a subtree itself.
If a binary tree is traversed in-order, the output will produce sorted key values in an
ascending order.
We start from A, and following in-order traversal, we move to its left subtree B.B is also
traversed in-order. The process goes on until all the nodes are visited. The output of in-order
traversal of this tree will be −
D→B→E→A→F→C→G
Algorithm
Until all nodes are traversed −
Pre-order Traversal
In this traversal method, the root node is visited first, then the left subtree and finally the right
subtree.
We start from A, and following pre-order traversal, we first visit A itself and then move to its
left subtree B. B is also traversed pre-order. The process goes on until all the nodes are
visited. The output of pre-order traversal of this tree will be −
A→B→D→E→C→F→G
Algorithm
Until all nodes are traversed −
Step 1 − Visit root node.
Step 2 − Recursively traverse left subtree.
Step 3 − Recursively traverse right subtree.
Post-order Traversal
In this traversal method, the root node is visited last, hence the name. First we traverse the
left subtree, then the right subtree and finally the root node.
We start from A, and following pre-order traversal, we first visit the left subtree B. B is also
traversed post-order. The process goes on until all the nodes are visited. The output of post-
order traversal of this tree will be −
D→E→B→F→G→C→A
Algorithm
Until all nodes are traversed −
Step 1 − Recursively traverse left subtree.
Step 2 − Recursively traverse right subtree.
Step 3 − Visit root node.
Data Structure - Binary Search Tree
A Binary Search Tree (BST) is a tree in which all the nodes follow the below-mentioned
properties −
1) The left sub-tree of a node has a key less than or equal to its parent node's key.
2) The right sub-tree of a node has a key greater than or equal to its parent node's key.
Thus, BST divides all its sub-trees into two segments; the left sub-tree and the right sub-tree
and can be defined as –
Representation
BST is a collection of nodes arranged in a way where they maintain BST properties. Each
node has a key and an associated value. While searching, the desired key is compared to the
keys in BST and if found, the associated value is retrieved.
We observe that the root node key (27) has all less-valued keys on the left sub-tree and the
higher valued keys on the right sub-tree.
Basic Operations
Following are the basic operations of a tree −
Search Operation
Whenever an element is to be searched, start searching from the root node. Then if the data is
less than the key value, search for the element in the left subtree. Otherwise, search for the
element in the right subtree. Follow the same algorithm for each node.
Algorithm
1. START
2. Check whether the tree is empty or not
3. If the tree is empty, search is not possible
4. Otherwise, first search the root of the tree.
5. If the key does not match with the value in the root, search its subtrees.
6. If the value of the key is less than the root value, search the left subtree
7. If the value of the key is greater than the root value, search the right subtree.
8. If the key is not found in the tree, return unsuccessful search.
9. END
Insert Operation
Whenever an element is to be inserted, first locate its proper location. Start searching from
the root node, then if the data is less than the key value, search for the empty location in the
left subtree and insert the data. Otherwise, search for the empty location in the right subtree
and insert the data.
Algorithm
1 – START
2 – If the tree is empty, insert the first element as the root node of the tree. The following
elements are added as the leaf nodes.
3 – If an element is less than the root value, it is added into the left subtree as a leaf node.
4 – If an element is greater than the root value, it is added into the right subtree as a leaf node.
5 – The final leaf nodes of the tree point to NULL values as their child nodes.
6 – END
Inorder Traversal
The inorder traversal operation in a Binary Search Tree visits all its nodes in the following order −
1) Firstly, we traverse the left child of the root node/current node, if any.
2) Next, traverse the current node.
3) Lastly, traverse the right child of the current node, if any.
Algorithm
1. START
2. Traverse the left subtree, recursively
3. Then, traverse the root node
4. Traverse the right subtree, recursively.
5. END
Preorder Traversal
The preorder traversal operation in a Binary Search Tree visits all its nodes. However, the
root node in it is first printed, followed by its left subtree and then its right subtree.
Algorithm
1. START
2. Traverse the root node first.
3. Then traverse the left subtree, recursively
4. Later, traverse the right subtree, recursively.
5. END
Postorder Traversal
Like the other traversals, postorder traversal also visits all the nodes in a Binary Search Tree
and displays them. However, the left subtree is printed first, followed by the right subtree and
lastly, the root node.
Algorithm
1. START
2. Traverse the left subtree, recursively
3. Traverse the right subtree, recursively.
4. Then, traverse the root node
5. END
Data Structure and Algorithms - AVL Trees
The first type of self-balancing binary search tree to be invented is the AVL tree. The name
AVL tree is coined after its inventor's names − Adelson-Velsky and Landis.
In AVL trees, the difference between the heights of left and right subtrees, known as
the Balance Factor, must be at most one. Once the difference exceeds one, the tree
automatically executes the balancing algorithm until the difference becomes one again.
LL Rotations
LL rotation is performed when the node is inserted into the right subtree leading to an
unbalanced tree. This is a single left rotation to make the tree balanced again –
The node where the unbalance occurs becomes the left child and the newly added node becomes
the right child with the middle node as the parent node.
RR Rotations
RR rotation is performed when the node is inserted into the left subtree leading to an
unbalanced tree. This is a single right rotation to make the tree balanced again −
The node where the unbalance occurs becomes the right child and the newly added node
becomes the left child with the middle node as the parent node.
LR Rotations
LR rotation is the extended version of the previous single rotations, also called a double
rotation. It is performed when a node is inserted into the right subtree of the left subtree. The
LR rotation is a combination of the left rotation followed by the right rotation. There are
multiple steps to be followed to carry this out.
1) Consider an example with “A” as the root node, “B” as the left child of “A” and “C”
as the right child of “B”.
2) Since the unbalance occurs at A, a left rotation is applied on the child nodes of A, i.e.
B and C.
3) After the rotation, the C node becomes the left child of A and B becomes the left child
of C.
4) The unbalance still persists, therefore a right rotation is applied at the root node A and
the left child C.
5) After the final right rotation, C becomes the root node, A becomes the right child and
B is the left child.
RL Rotations
RL rotation is also the extended version of the previous single rotations, hence it is called a
double rotation and it is performed if a node is inserted into the left subtree of the right
subtree. The RL rotation is a combination of the right rotation followed by the left rotation.
There are multiple steps to be followed to carry this out.
1) Consider an example with “A” as the root node, “B” as the right child of “A” and “C”
as the left child of “B”.
2) Since the unbalance occurs at A, a right rotation is applied on the child nodes of A,
i.e. B and C.
3) After the rotation, the C node becomes the right child of A and B becomes the right
child of C.
4) The unbalance still persists, therefore a left rotation is applied at the root node A and
the right child C.
5) After the final left rotation, C becomes the root node, A becomes the left child and B
is the right child.
BASIC OPERATIONS OF AVL TREES
The basic operations performed on the AVL Tree structures include all the operations
performed on a binary search tree, since the AVL Tree at its core is actually just a binary
search tree holding all its properties. Therefore, basic operations performed on an AVL Tree
are − Insertion and Deletion.
Insertion
The data is inserted into the AVL Tree by following the Binary Search Tree property of
insertion, i.e. the left subtree must contain elements less than the root value and right subtree
must contain all the greater elements. However, in AVL Trees, after the insertion of each
element, the balance factor of the tree is checked; if it does not exceed 1, the tree is left as it
is. But if the balance factor exceeds 1, a balancing algorithm is applied to readjust the tree
such that balance factor becomes less than or equal to 1 again.
Algorithm
The following steps are involved in performing the insertion operation of an AVL Tree −
Step 1 − Create a node
Step 2 − Check if the tree is empty
Step 3 − If the tree is empty, the new node created will become the root node of the AVL
Tree.
Step 4 − If the tree is not empty, we perform the Binary Search Tree insertion operation and
check the balancing factor of the node in the tree.
Step 5 − Suppose the balancing factor exceeds ±1, we apply suitable rotations on the said
node and resume the insertion from Step 4.
Deletion
Deletion in the AVL Trees take place in three different scenarios −
Scenario 1 (Deletion of a leaf node) − If the node to be deleted is a leaf node, then it is
deleted without any replacement as it does not disturb the binary search tree property.
However, the balance factor may get disturbed, so rotations are applied to restore it.
Scenario 2 (Deletion of a node with one child) − If the node to be deleted has one child,
replace the value in that node with the value in its child node. Then delete the child node. If
the balance factor is disturbed, rotations are applied.
Scenario 3 (Deletion of a node with two child nodes) − If the node to be deleted has two
child nodes, find the inorder successor of that node and replace its value with the inorder
successor value. Then try to delete the inorder successor node. If the balance factor exceeds 1
after deletion, apply balance algorithms.
TRY QUESTIONS
1. A procedure that calls itself is called
A. Illegal call
B. None of the above
C. Recursive
D. Reverse polish
2. Which one of the below mentioned is linear data structure?
A. Arrays
B. Queue
C. Stack
D. All of the above
3. Stack is used for ________________
A. Breadth First Traversal
B. CPU Resource Allocation
C. Recursion
D. None of the above
4. If the elements “A”, “B”, “C” and “D” are placed in a queue and are deleted one at a
time, in what order will they be removed?
A. ABCD
B. ABDC
C. DCAB
D. DCBA
5. Which of the following uses FIFO method?
A. Binary Search Tree
B. Hash Table
C. Queue
D. Stack
30. Which of the following sorting algorithms provide the best time complexity in the
worst-case scenario?
A. Merge Sort
B. Quick Sort
C. Bubble Sort
D. Selection Sort
31. Which of the following is true about the characteristics of abstract data types?
i. It exports a type.
ii. It exports a set of operations
A. False, False
B. False, True
C. True, False
D. True, True
32. In an inorder traversal a node is visited after its left subtree and before its right subtree
A. True
B. False
33. In a postorder traversal, a node is visited before and after its descendants
A. True
B. False
34. A format for arranging operators and operands in a way in which the operators are
always placed after the operands, is known as __________________________
35. __________________________ is an operation that can be performed on a queue in
which an element at the very front of the queue is removed or accessed for an operation.
36. What is the term for inserting into a full queue? __________________________
37. What will be the value of top, if there is a size of stack (STACK_SIZE) = 5? _________
38. The logical or mathematical model of a particular organization of data is called a
__________________________
39. The maximum number of nodes on depth i of a binary tree is __________________.
40. A full binary tree of a given height k has __________________________ nodes.
ASSIGNMENT
Q1. Assuming that a queue implemented in a circular fashion is in the state shown in the
diagram below. Draw a diagram showing the structure after the letters G and R are
inserted, three letters are removed, and the letters D and P are inserted.
b) What error will occur if the letters G, R, D, and P are inserted before any letters are
removed?
c) List the 4 types of queues and briefly describe any two of them
d) Write a simple Java implementation of a queue to enqueue the integer items:
2,4,6,8,10. Print out the items in the queue
Q2. Explain stack data type and state how different it is from queue
b) Write an algorithm for a push operation
c) Taking Y as a 4x5 array with base element BA= 43. Find the address of
element A(3,4) when the storage is row major
d) Evaluate the following expressions
i. A + B * C to postfix form
ii. (A + B) * C to prefix form
Q3. Briefly explain the following terms
b) Consider the tree below and traverse them as follows:
i. Preorder traversal
ii. Postorder traversal
iii. Inorder traversal
c) Explain the time complexity of an algorithm
d) Taking A as a 5x6 array with base element BA=2146. If each element of the
array occupies 3 words. Find the address of element A(3,5) when the
arrangement is column major