0% found this document useful (0 votes)
1 views56 pages

NOTE_071520

The document provides an introduction to data structures and algorithms, explaining their importance in organizing and managing data efficiently. It categorizes data structures into primitive and non-primitive types, and discusses linear and non-linear structures, as well as algorithm characteristics and analysis. Additionally, it covers algorithm complexity, asymptotic analysis, and specific algorithmic approaches like greedy algorithms and divide-and-conquer.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
0% found this document useful (0 votes)
1 views56 pages

NOTE_071520

The document provides an introduction to data structures and algorithms, explaining their importance in organizing and managing data efficiently. It categorizes data structures into primitive and non-primitive types, and discusses linear and non-linear structures, as well as algorithm characteristics and analysis. Additionally, it covers algorithm complexity, asymptotic analysis, and specific algorithmic approaches like greedy algorithms and divide-and-conquer.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 56

INTRODUCTION TO DATA STRUCTURE

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.

TYPES OF DATA STRUCTURES:


Data structures can be classified into two main types: primitive data structures and non-
primitive data structures.
1) Primitive data structures: These are the most basic data structures and are usually
built into programming languages. Examples include:
Integer, Float, Character, Boolean, Double, Void etc.
2) Non-primitive data structures: These are complex data structures that are built
using primitive data types. Non-primitive data structures can be further categorized
into the following types:
Arrays, Linked lists, Stacks, Queues, Trees , Graphs ,Hash tables etc.

CLASSIFICATION OF DATA STRUCTURES


1) Linear data structure: Data structure in which data elements are arranged
sequentially or linearly, where each element is attached to its previous and next
adjacent elements, is called a linear data structure.
Examples of linear data structures are array, stack, queue, linked list, etc.
A) Static data structure: Static data structure has a fixed memory size. It is
easier to access the elements in a static data structure.
An example of this data structure is an array.
B) Dynamic data structure: In dynamic data structure, the size is not fixed.
It can be randomly updated during the runtime which may be considered
efficient concerning the memory (space) complexity of the code.
Examples of this data structure are queue, stack, etc.
2) Non-linear data structure: Data structures where data elements are not placed
sequentially or linearly are called non-linear data structures. In a non-linear data
structure, we can’t traverse all the elements in a single run only.
Examples of non-linear data structures are trees and graphs.

IMPORTANT OF DATA STRUCTURES


1) Efficient data processing: Data structures provide a way to organize and store data
in a way that allows for efficient retrieval, manipulation, and storage of data. For
example, using a hash table to store data can provide constant-time access to data.
2) Memory management: Proper use of data structures can help to reduce memory
usage and optimize the use of resources. For example, using dynamic arrays can allow
for more efficient use of memory than using static arrays.
3) Code reusability: Data structures can be used as building blocks in various
algorithms and programs, making it easier to reuse code.
4) Abstraction: Data structures provide a level of abstraction that allows programmers
to focus on the logical structure of the data and the operations that can be performed
on it, rather than on the details of how the data is stored and manipulated.
5) Algorithm design: Many algorithms rely on specific data structures to operate
efficiently. Understanding data structures is crucial for designing and implementing
efficient algorithms.
CHAPTER TWO
ALGORITHM
What is an Algorithm?
An algorithm is a set of well-defined instructions to solve a particular problem. It takes a set
of input(s) and produces the desired output.
From the data structure point of view, following are some important categories of algorithms
A) Search − Algorithm to search an item in a data structure.
B) Sort − Algorithm to sort items in a certain order.
C) Insert − Algorithm to insert item in a data structure.
D) Update − Algorithm to update an existing item in a data structure.
E) Delete − Algorithm to delete an existing item from a data structure.

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.

How to Write an Algorithm?


There are no well-defined standards for writing algorithms. Rather, it is problem and resource
dependent. Algorithms are never written to support a particular programming code.

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.

Writing step numbers, is optional.


ALGORITHM ANALYSIS
Efficiency of an algorithm can be analysed at two different stages, before implementation and
after implementation. They are the following −

1) A Priori Analysis − This is a theoretical analysis of an algorithm. Efficiency of an


algorithm is measured by assuming that all other factors, for example, processor
speed, are constant and have no effect on the implementation.
2) A Posterior Analysis − This is an empirical analysis of an algorithm. The selected
algorithm is implemented using programming language. This is then executed on
target computer machine. In this analysis, actual statistics like running time and space
required, are collected.
We shall learn about a priori algorithm analysis. Algorithm analysis deals with the execution
or running time of various operations involved. The running time of an operation can be
defined as the number of computer instructions executed per operation.

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 −

Algorithm: SUM (A, B)

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 −

➢ Best Case − Minimum time required for program execution.


➢ Average Case − Average time required for program execution.
➢ Worst Case − Maximum time required for program execution.

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)

n log n Ο(n log n)

quadratic Ο(n2)

cubic Ο(n3)

polynomial nΟ(1)

exponential 2Ο(n)

DATA STRUCTURES - GREEDY ALGORITHMS


An algorithm is designed to achieve optimum solution for a given problem. In greedy
algorithm approach, decisions are made from the given solution domain. As being greedy, the
closest solution that seems to provide an optimum solution is chosen.

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 −

1 − Select one 10 coin, the remaining count is 8

2 − Then select one 5 coin, the remaining count is 3

3 − Then select one 2 coin, the remaining count is 1

4 − And finally, the selection of one 1 coins solves the problem


Though, it seems to be working fine, for this count we need to pick only 4 coins. But if we
slightly change the problem then the same approach may not be able to produce the same
optimum result.

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 −

➢ Travelling Salesman Problem


➢ Prim's Minimal Spanning Tree Algorithm
➢ Kruskal's Minimal Spanning Tree Algorithm
➢ Dijkstra's Minimal Spanning Tree Algorithm
➢ Graph - Map Coloring

DATA STRUCTURES - DIVIDE AND CONQUER


In divide and conquer approach, the problem in hand, is divided into smaller sub-problems
and then each problem is solved independently. When we keep on dividing the sub-problems
into even smaller sub-problems, we may eventually reach a stage where no more division is
possible. Those "atomic" smallest possible sub-problem (fractions) are solved. The solutionof
all sub-problems is finally merged in order to obtain the solution of an original problem.
Broadly, we can understand divide-and-conquer approach in a three-step process.
1) Divide/Break: This step involves breaking the problem into smaller sub-problems.
Sub-problems should represent a part of the original problem. This step generally
takes a recursive approach to divide the problem until no sub-problem is further
divisible. At this stage, sub-problems become atomic in nature but still represent some
part of the actual problem.
2) Conquer/Solve: This step receives a lot of smaller sub-problems to be solved.
Generally, at this level, the problems are considered 'solved' on their own.
3) Merge/Combine: When the smaller sub-problems are solved, this stage recursively
combines them until they formulate a solution of the original problem. This
algorithmic approach works recursively and conquer & merge steps works so close
that they appear as one.

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)

DATA STRUCTURES - DYNAMIC PROGRAMMING


Dynamic programming approach is similar to divide and conquer in breaking down the
problem into smaller and yet smaller possible sub-problems. But unlike, divide and conquer,
these sub-problems are not solved independently. Rather, results of these smaller sub-
problems are remembered and used for similar or overlapping sub-problems.

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.

So we can say that −

1) The problem should be able to be divided into smaller overlapping sub-problem.


2) An optimum solution can be achieved by using an optimum solution of smaller sub-
problems.
3) Dynamic algorithms use Memorization.

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 −

➢ Fibonacci number series


➢ Knapsack problem
➢ Tower of Hanoi
➢ All pair shortest path by Floyd-Warshall
➢ Shortest path by Dijkstra

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

The classification of algorithms is important for several reasons:


1) Organization: Algorithms can be very complex and by classifying them, it becomes
easier to organize, understand, and compare different algorithms.
2) Problem Solving: Different problems require different algorithms, and by having a
classification, it can help identify the best algorithm for a particular problem.
3) Performance Comparison: By classifying algorithms, it is possible to compare their
performance in terms of time and space complexity, making it easier to choose the
best algorithm for a particular use case.
4) Reusability: By classifying algorithms, it becomes easier to re-use existing
algorithms for similar problems, thereby reducing development time and improving
efficiency.
5) Research: Classifying algorithms is essential for research and development in
computer science, as it helps to identify new algorithms and improve existing ones.

Overall, the classification of algorithms plays a crucial role in computer science and helps to
improve the efficiency and effectiveness of solving problems.

A) Classification by Implementation Method: There are primarily three main categories


into which an algorithm can be named in this type of classification. They are:
1) Recursion or Iteration: A recursive algorithm is an algorithm which calls itself again
and again until a base condition is achieved whereas iterative algorithms
use loops and/or data structures like stacks, queues to solve any problem. Every
recursive solution can be implemented as an iterative solution and vice versa.
Example: The Tower of Hanoi is implemented in a recursive fashion while Stock
Span problem is implemented iteratively.

2) Exact or Approximate: Algorithms that are capable of finding an optimal solution


for any problem are known as the exact algorithm. For all those problems, where it is
not possible to find the most optimized solution, an approximation algorithm is used.
Approximate algorithms are the type of algorithms that find the result as an average
outcome of sub outcomes to a problem.
Example: For NP-Hard Problems, approximation algorithms are used. Sorting
algorithms are the exact algorithms.
3) Serial or Parallel or Distributed Algorithms: In serial algorithms, one instruction is
executed at a time while parallel algorithms are those in which we divide the problem
into sub-problems and execute them on different processors. If parallel algorithms are
distributed on different machines, then they are known as distributed algorithms.

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.

Classification by Design Approaches:


There are two approaches for designing an algorithm. these approaches include:
1) Top-Down Approach: In the top-down approach, a large problem is divided into
small sub-problem. and keep repeating the process of decomposing problems until the
complex problem is solved.
2) Bottom-up approach: The bottom-up approach is also known as the reverse of top-
down approaches.
In approach different, part of a complex program is solved using a programming
language and then this is combined into a complete program.

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

ALGORITHM 2: Find the largest number among three numbers


Step 1: Start
Step 2: Declare variables a,b and c.
Step 3: Read variables a,b and c.
Step 4: If a > b
If a > c
Display a is the largest number.
Else
Display c is the largest number.
Else
If b > c
Display b is the largest number.
Else
Display c is the greatest number.
Step 5: 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).

Following are the important terms to understand the concept of Array.


1) Element − Each item stored in an array is called an element.
2) Index − Each location of an element in an array has a numerical index, which is used
to identify the element.

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.

1) Index starts with 0.


2) Array length is 6 which means it can store 6 elements.
3) Each element can be accessed via its index. For example, we can fetch an element at
index 2 as 10.

Array Basic Operations


Following are the basic operations supported by an array.

A) Traverse − print all the array elements one by one.


B) Insertion − Adds an element at the given index.
C) Deletion − Deletes an element at the given index.
D) Search − Searches an element using the given index or by the value.
E) Update − Updates an element at the given index.

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

Following is the implementation of the above algorithm −

#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";

for(i = 0; i<n; i++) {


cout<<"LA[%d] = %d \n"<< i<<LA[i]);
}
n = n + 1;

while(j>= k) {
LA[j+1] = LA[j];
j = j - 1;
}

LA[k] = item;

cout<<"The array elements after insertion:\n";

for(i = 0; i<n; i++) {


cout<<"LA[%d] = %d \n"<< i<< LA[i]);
}
}

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;

printf("The original array elements are :\n");

for(i = 0; i<n; i++) {


printf("LA[%d] = %d \n", i, LA[i]);
}

j = k;

while( j < n) {
LA[j-1] = LA[j];
j = j + 1;
}

n = n -1;

printf("The array elements after deletion :\n");

for(i = 0; i<n; i++) {


printf("LA[%d] = %d \n", i, LA[i]);
}
}
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 deletion :
LA[0] = 1
LA[1] = 3
LA[2] = 7
LA[3] = 8
Applications of Array Data Structure:
Below are some applications of arrays.
1) Storing and accessing data: Arrays are used to store and retrieve data in a specific
order. For example, an array can be used to store the scores of a group of students, or
the temperatures recorded by a weather station.
2) Sorting: Arrays can be used to sort data in ascending or descending order. Sorting
algorithms such as bubble sort, merge sort, and quicksort rely heavily on arrays.
3) Searching: Arrays can be searched for specific elements using algorithms such as
linear search and binary search.
4) Matrices: Arrays are used to represent matrices in mathematical computations such
as matrix multiplication, linear algebra, and image processing.
5) Stacks and queues: Arrays are used as the underlying data structure for
implementing stacks and queues, which are commonly used in algorithms and data
structures.
6) Graphs: Arrays can be used to represent graphs in computer science. Each element in
the array represents a node in the graph, and the relationships between the nodes are
represented by the values stored in the array.
7) Dynamic programming: Dynamic programming algorithms often use arrays to store
intermediate results of sub-problems in order to solve a larger problem.

Data Structure and Algorithms - Linked List


If arrays accommodate similar types of data types, linked lists consist of elements with
different data types that are also arranged sequentially.

But how are these linked lists created?

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.

There are three types of linked lists −

➢ 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.

Singly Linked Lists


Singly linked lists contain two “buckets” in one node; one bucket holds the data and the other
bucket holds the address of the next node of the list. Traversals can be done in one direction
only as there is only a single link between two nodes of the same list.
Doubly Linked Lists
Doubly Linked Lists contain three “buckets” in one node; one bucket holds the data and the
other buckets hold the addresses of the previous and next nodes in the list. The list is
traversed twice as the nodes in the list are connected to each other from both sides.

Circular Linked Lists


Circular linked lists can exist in both singly linked list and doubly linked list.
Since the last node and the first node of the circular linked list are connected, the traversal in
this linked list will go on forever until it is broken.

Basic Operations in the Linked Lists


The basic operations in the linked lists are insertion, deletion, searching, display, and deleting
an element at a given key. These operations are performed on Singly Linked Lists as given
below −
➢ Insertion − Adds an element at the beginning of the list.
➢ Deletion − Deletes an element at the beginning of the list.
➢ Display − Displays the complete list.
➢ Search − Searches an element using the given key.
➢ Delete − Deletes an element using the given key.
Insertion Operation
Adding a new node in linked list is a more than one step activity. We shall learn this with
diagrams here. First, create a node using the same structure and find the location where it has
to be inserted.

Imagine that we are inserting a node B (NewNode), between A (LeftNode) and C


(RightNode). Then point B.next to C −
NewNode.next −> RightNode;
It should look like this −

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)

As multiplication operation has precedence over addition, b * c will be evaluated first.


A table of operator precedence is provided later.
Associativity
Associativity describes the rule where operators with the same precedence appear in an
expression. For example, in expression a + b − c, both + and – have the same precedence,
then which part of the expression will be evaluated first, is determined by associativity of
those operators. Here, both + and − are left associative, so the expression will be evaluated
as (a + b) − c.

Precedence and associativity determines the order of evaluation of an expression. Following


is an operator precedence and associativity table (highest to lowest) −

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

Data Structure and Algorithms - Queue


Queue, like Stack, is also an abstract data structure. The thing that makes queue different
from stack is that a queue is open at both its ends. Hence, it follows FIFO (First-In-First-Out)
structure, i.e. the data item inserted first will also be accessed first. The data is inserted into
the queue through one end and deleted from it using the other end.

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

1. Circular Queue: Circular Queue is a linear data structure in which the


operations are performed based on FIFO (First In First Out) principle and the
last position is connected back to the first position to make a circle. It is also
called ‘Ring Buffer’. This queue is primarily used in the following cases:
1. Memory Management: The unused memory locations in the case of
ordinary queues can be utilized in circular queues.
2. Traffic system: In a computer-controlled traffic system, circular queues
are used to switch on the traffic lights one by one repeatedly as per the
time set.
3. CPU Scheduling: Operating systems often maintain a queue of
processes that are ready to execute or that are waiting for a particular
event to occur.
The time complexity for the circular Queue is O(1).
2. Input restricted Queue: In this type of Queue, the input can be taken
from one side only(rear) and deletion of elements can be done from both
sides(front and rear). This kind of Queue does not follow FIFO(first in first
out). This queue is used in cases where the consumption of the data needs
to be in FIFO order but if there is a need to remove the recently inserted data
for some reason and one such case can be irrelevant data, performance
issue, etc.
Advantages of Input restricted Queue:
• Prevents overflow and overloading of the queue by limiting the number of
items added
• Helps maintain stability and predictable performance of the system
Disadvantages of Input restricted Queue:
• May lead to resource wastage if the restriction is set too low and items
are frequently discarded
• May lead to waiting or blocking if the restriction is set too high and the
queue is full, preventing new items from being added.
3. Output restricted Queue: In this type of Queue, the input can be taken
from both sides(rear and front) and the deletion of the element can be done
from only one side(front). This queue is used in the case where the inputs
have some priority order to be executed and the input can be placed even in
the first place so that it is executed first.

4. Double ended Queue: Double Ended Queue is also a Queue data


structure in which the insertion and deletion operations are performed
at both the ends (front and rear). That means, we can insert at both
front and rear positions and can delete from both front and rear
positions. Since Deque supports both stack and queue operations, it
can be used as both. The Deque data structure supports clockwise
and anticlockwise rotations in O(1) time which can be useful in certain
applications. Also, the problems where elements need to be removed
and or added both ends can be efficiently solved using Deque.
5. Priority Queue: A priority queue is a special type of queue in which each
element is associated with a priority and is served according to its priority.
There are two types of Priority Queues. They are:
1. Ascending Priority Queue: Element can be inserted arbitrarily but only
smallest element can be removed. For example, suppose there is an
array having elements 4, 2, 8 in the same order. So, while inserting the
elements, the insertion will be in the same sequence but while deleting,
the order will be 2, 4, 8.
2. Descending priority Queue: Element can be inserted arbitrarily but only
the largest element can be removed first from the given Queue. For
example, suppose there is an array having elements 4, 2, 8 in the same
order. So, while inserting the elements, the insertion will be in the same
sequence but while deleting, the order will be 8, 4, 2.
The time complexity of the Priority Queue is O(logn).
Applications of a Queue:
The queue is used when things don’t have to be processed immediately, but
have to be processed in First In First Out order like Breadth First Search.
This property of Queue makes it also useful in the following kind of
scenarios.
1. When a resource is shared among multiple consumers. Examples
include CPU scheduling, Disk Scheduling.
2. When data is transferred asynchronously (data not necessarily received
at the same rate as sent) between two processes. Examples include IO
Buffers, pipes, file IO, etc.
3. Linear Queue: A linear queue is a type of queue where data elements are
added to the end of the queue and removed from the front of the queue.
Linear queues are used in applications where data elements need to be
processed in the order in which they are received. Examples include
printer queues and message queues.
4. Circular Queue: A circular queue is similar to a linear queue, but the end
of the queue is connected to the front of the queue. This allows for
efficient use of space in memory and can improve performance. Circular
queues are used in applications where the data elements need to be
processed in a circular fashion. Examples include CPU scheduling and
memory management.
5. Priority Queue: A priority queue is a type of queue where each element is
assigned a priority level. Elements with higher priority levels are
processed before elements with lower priority levels. Priority queues are
used in applications where certain tasks or data elements need to be
processed with higher priority. Examples include operating system task
scheduling and network packet scheduling.
6. Double-ended Queue: A double-ended queue, also known as a deque, is
a type of queue where elements can be added or removed from either
end of the queue. This allows for more flexibility in data processing and
can be used in applications where elements need to be processed in
multiple directions. Examples include job scheduling and searching
algorithms.
7. Concurrent Queue: A concurrent queue is a type of queue that is
designed to handle multiple threads accessing the queue simultaneously.
Concurrent queues are used in multi-threaded applications where data
needs to be shared between threads in a thread-safe manner. Examples
include database transactions and web server requests.

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

Deletion Operation: dequeue()


The dequeue() is a data manipulation operation that is used to remove elements from the
stack. The following algorithm describes the dequeue() operation in a simpler way.
Algorithm
1 – START
2 − Check if the queue is empty.
3 − If the queue is empty, produce underflow error and exit.
4 − If the queue is not empty, access the data where front is pointing.
5 − Increment front pointer to point to the next available data element.
6 − Return success.
7 – END

The peek() Operation


The peek() is an operation which is used to retrieve the front most element in the queue,
without deleting it. This operation is used to check the status of the queue with the help of the
pointer.

Algorithm
1 – START
2 – Return the element at the front of the queue
3 – END

The isFull() Operation


The isFull() operation verifies whether the stack is full.
Algorithm
1 – START
2 – If the count of queue elements equals the queue size, return true
3 – Otherwise, return false
4 – END

The isEmpty() operation


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 count of queue elements equals zero, return true
3 – Otherwise, return false
4 – END
DATA STRUCTURE AND ALGORITHMS - TREE
A tree is a non-linear abstract data type with a hierarchy-based structure. It consists of nodes
(where the data is stored) that are connected via links. The tree data structure stems from a
single node called a root node and has subtrees connected to the root.

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

There are three 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.

Full Binary Tree


❖ A full binary tree is a binary tree type where every node has either 0 or 2 child nodes.
Complete Binary Tree
❖ A complete binary tree is a binary tree type where all the leaf nodes must be on the
same level. However, root and internal nodes in a complete binary tree can either have
0, 1 or 2 child nodes.
Perfect Binary Tree
❖ A perfect binary tree is a binary tree type where all the leaf nodes are on the same
level and every node except leaf nodes have 2 children.

Binary Search Trees


Binary Search Trees possess all the properties of Binary Trees including some extra
properties of their own, based on some constraints, making them more efficient than binary
trees.

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.

Balanced Binary Search Trees


Consider a Binary Search Tree with ‘m’ as the height of the left subtree and ‘n’ as the height
of the right subtree. If the value of (m-n) is equal to 0,1 or -1, the tree is said to be a Balanced
Binary Search Tree.
The trees are designed in a way that they self-balance once the height difference exceeds 1.
Binary Search Trees use rotations as self-balancing algorithms. There are four different types
of rotations: Left Left, Right Right, Left Right, Right Left.
There are various types of self-balancing binary search trees −
A) AVL Trees
B) Red Black Trees
C) B Trees
D) B+ Trees
E) Splay Trees
F) Priority Search Trees

Data Structure & Algorithms - Tree Traversal


Traversal is a process to visit all the nodes of a tree and may print their values too. Because,
all nodes are connected via edges (links) we always start from the root (head) node. That is,
we cannot randomly access a node in a tree. There are three ways which we use to traverse a
tree −

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 −

Step 1 − Recursively traverse left subtree.

Step 2 − Visit root node.

Step 3 − Recursively traverse right subtree.

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 –

left_subtree (keys) ≤ node (key) ≤ right_subtree (keys)

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.

Following is a pictorial representation of BST –

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 −

1) Search − Searches an element in a tree.


2) Insert − Inserts an element in a tree.
3) Pre-order Traversal − Traverses a tree in a pre-order manner.
4) In-order Traversal − Traverses a tree in an in-order manner.
5) Post-order Traversal − Traverses a tree in a post-order manner

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.

BALANCE FACTOR = HEIGHT(LEFT SUBTREE) – HEIGHT(RIGHT SUBTREE)


There are usually four cases of rotation in the balancing algorithm of AVL trees: LL, RR, LR,
RL.

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

6. Which of the following is not the part of ADT description?


A. Data
B. Operations
C. Both of the above
D. None of the above
7. Which of the following refers to a node without a parent?
A. Tree
B. Root
C. Ancestor
D. Subtree
8. If a normal queue is implemented using an array of size MAX_SIZE, it gets full
when_____________
A. Front = (rear + 1)mod MAX_SIZE
B. Front = rear + 1
C. Rear = front
D. Rear = MAX_SIZE – 1
9. Trying to remove an element from an empty stack result in an _________
A. Empty collection Condition
B. Garbage Collection Condition
C. Overflow Condition
D. Underflow Condition
10. Which of the following is not the type of queue?
A. Circular queue
B. Ordinary queue
C. Priority queue
D. Single ended queue
11. Identify the data structure which allows deletions at both ends of the list but insertion
at only one end
A. Input restricted dequeue
B. Output restricted dequeue
C. Priority queues
D. Stack
Page 4 of 9
12. _____________________________ is the task of visiting all nodes of the tree at least
once
A. Traversing
B. Searching
C. Accessing
D. Selecting
13. Entries in a stack are “ordered”. What is the meaning of this statement?
A. A collection of stacks is sortable
B. Stack entries may be compared with the ‘<‘ operation
C. The entries are stored in a linked list
D. There is a Sequential entry that is one by one
14. What is the value of the postfix expression 6 3 2 4 + – *?
A. -18
B. 1
C. 40
D. 74
15. What is the time complexity to insert an element to the front of a LinkedList(head
pointer given)?
A. O(n)
B. O(1)
C. O(logn)
D. O(n*logn)
16. In a circular queue the value of r will be...
A. r=(r+1)% [QUEUE_SIZE – 1]
B. r=(r+1)% QUEUE_SIZE
C. r=(r-1)% QUEUE_SIZE
D. r=r+1
17. The postfix form of the expression (A+ B)*(C*D- E)*F / G is?
A. AB + CD* E – *F *G /
B. AB + CD* E – F **G /
C. AB + CDE * – * F *G /
D. AB+ CD*E – FG /**
Page 5 of 9
18. What data structure would you mostly likely see in non-recursive implementation of a
recursive algorithm?
A. Linked List
B. Queue
C. Stack
D. Tree
19. Which of the following statement(s) about stack data structure is/are NOT correct?
A. Linked List are used for implementing Stacks
B. Null link is present in the last node at the bottom of the stack
C. Stack is the FIFO data structure
D. Top of the Stack always contain the new node
20. The postfix form of A*B+C/D is?
A. *AB/CD+
B. A*BC+/D
C. AB*CD/+
D. ABCD+/*
21. Which data structure is needed to convert infix notation to postfix notation?
A. Branch
B. Queue
C. Stack
D. Tree
22. _____ is a special kind of complete binary tree which stores its nodes in a partial order
A. Queue
B. Heap
C. Stack
D. Tree
23. Which of the following represents the Postorder Traversal of a Binary Tree?
A. Left → Right →Root
B. Left→Root→Right
C. Right→Left→Root
D. Right→Root→Left
Page 6 of 9
24. The prefix form of an infix expression (p + q) – (r * t) is?
A. – + * pqrt
B. – +pq * rt
C. – +pqr * t
D. + pq – *rt
25. A collection of two or more adjacent memory locations containing the same type of
data.
A. Array
B. List
C. Queue
D. Linked list
26. Which of the following is not an inherent application of stack?
A. Evaluation of postfix expression
B. Implementation of recursion
C. Job scheduling
D. Reversing a string
27. What is a dequeue?
A. A queue implemented with a doubly linked list
B. A queue implemented with both singly and doubly linked lists
C. A queue with insert/delete defined for both front and rear ends of the queue
D. A queue with insert/delete defined for front side of the queue
28. Which of the following data structure store the homogeneous data elements?
A. Arrays
B. Lists
C. Pointers
D. Records
29. A linear list of elements in which deletion can be done from one end (front) and
insertion can take place only at the other end (rear) is known as ____________
A. Linked list
B. Queue
C. Stack
D. Tree

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

You might also like