ds unit1
ds unit1
Unit-1
Introduction: Basic Terminology, Elementary Data Organization, Built in Data
Types in C. Algorithm, Efficiency of an Algorithm, Time and Space
Complexity, Asymptotic notations: Big Oh, Big Theta and Big Omega, Time-
Space trade-off. Abstract Data Types (ADT)
Arrays: Definition, Single and Multidimensional Arrays, Representation of
Arrays: Row Major Order, and Column Major Order, Derivation of Index
Formulae for 1-D,2-D,3-D and n-D Array Application of arrays, Sparse
Matrices and their representations.
Linked lists: Array Implementation and Pointer Implementation of Singly
Linked Lists, Doubly Linked List, Circularly Linked List, Operations on a
Linked List. Insertion, Deletion, Traversal, Polynomial Representation and
Addition Subtraction & Multiplications of Single variable & Two variables
Polynomial.
Introduction
Definition:
Data Structure is a systematic way to organize data in order to use it efficiently.
Data Structure=Organized data +Allowed operations
Following terms are the foundation terms of a data structure.
• Interface − Each data structure has an interface. Interface represents the set of operations that
a data structure supports. An interface only provides the list of supported operations, type of
parameters they can accept and return type of these operations.
• Implementation − Implementation provides the internal representation of a data structure.
Implementation also provides the definition of the algorithms used in the operations of the data
structure.
The term data structure is used to describe the way data is stored.
To develop a program of an algorithm we should select an appropriate data structure for that algorithm.
Therefore, data structure is represented as:
Data Structure is a way of collecting and organising data in such a way that we can perform operations
on these data in an effective way. Data Structures is about rendering data elements in terms of some
relationship, for better organization and storage. For example, we have some data which has
player's name "Virat" and age 26. Here "Virat" is of String data type and 26 is of integer data type.
In simple language, Data Structures are structures programmed to store ordered data, so that various
operations can be performed on it easily. It represents the knowledge of data to be organized in memory.
It should be designed and implemented in such a way that it reduces the complexity and increases the
efficiency.
Data items that are divided into sub-items are called Group items.
Group items Ex: An Employee Namemay be divided into three sub-items- first name,
middle name, and last name.
Elementary Data items that are not able to divide into sub-items are called Elementary items.
items Ex: SSN
Entity:
Ex: Attributes- Names, Age, Sex, SSN
Values- Rohland Gail, 34, F, 134-34-5533
Entities with similar attributes form an entity set. Each attribute of an entity set
Entity Set: has a range of values, the set of all possible values that could be assigned to the
particular attribute.
File Each record in a file may contain many field items but the value in a certain field
may uniquelydetermine the record in the file. Such a field K is called a primary
key and the values k1, k2, ….. in such a field are called keys or key values.
The study of complex data structures includes the following three steps:
The collection of data you work within a program have some kind of structure or organization.
No matter how complex your data structures are, they can be broken down into two fundamental types:
• Contiguous
• Non-Contiguous.
In contiguous structures, terms of data are kept together in memory (either RAM or in a file). An array
is an example of a contiguous structure. Since each element in the array is located next to one or two
other elements.
In contrast, items in a non-contiguous structure and scattered in memory, but we linked to each other
in some way. A linked list is an example of a non- contiguous data structure. Here, the nodes of the list
are linked together using pointers stored in each node.
Figure 1.2 below illustrates the difference between contiguous and non-contiguous structures.
Contiguous structures:
For example, a person‘s age can be represented with a simple integer that occupies two bytes of memory.
But his or her name, represented as a string of characters, may require many bytes and may even be of
varying length.
Couples with the atomic types (that is, the single data-item built-in types such as integer, float and
pointers), arrays and structs provide all the ―mortar‖ you need to built more exotic form of data structure,
including the non-contiguous forms.
Non-contiguous structures:
Non-contiguous structures are implemented as a collection of data-items, called nodes, where each node
can point to one or more other nodes in the collection.
The simplest kind of non-contiguous structure is linked list. A linked list represents a linear, one-
dimension type of non-contiguous structure, where there is only the notation of backwards and forwards.
as shown in figure 1.4(a. Linked List)
A tree such as shown in figure 1.4(b. Tree) is an example of a two-dimensional non-contiguous structure.
Here, there is the notion of up and down and left and right. In a tree each node has only one link that
leads into the node and links can only go do the tree. The most general type of non-contiguous structure,
called a graph has no such restrictions. Figure 1.4(c. Graph) is an example of a graph.
Hybrid structures:
If two basic types of structures are mixed then it is a hybrid form. Then one part contiguous and another
part non-contiguous. For example, figure 1.5 shows how to implement a double–linked list using three
parallel arrays, possibly stored a past from each other in memory.
The array D contains the data for the list, whereas the array P and N hold the previous andnext
―pointers‘‘. The pointers are actually nothing more than indexes into the D array. For instance, D[i]
holds the data for node i and p[i] holds the index to the node previous to i, where may or may not reside
at position i–1. Like wise, N[i] holds the index to the next node in the list.
What is abstract data type?
• An abstract data type is an abstraction of a data structure that provides only the interface to which
the data structure must adhere.
• The interface does not give any specific details about something should be implemented or in
what programming language.
• In other words, we can say that abstract data types are the entities that are definitions of data and
operations but do not have implementation details. In this case, we know the data that we are
storing and the operations that can be performed on the data, but we don't know about the
implementation details. The reason for not having implementation details is that every
programming language has a different implementation strategy for example; a C data structure
is implemented using structures while a C++ data structure is implemented using objects and
classes.
For example, a List is an abstract data type that is implemented using a dynamic array and linked list.
A queue is implemented using linked list-based queue, array-based queue, and stack-based queue. A
Map is implemented using Tree map, hash map, or hash table.
Abstract data type model
Before knowing about the abstract data type model, we should know about abstraction and
encapsulation.
Abstraction: It is a technique of hiding the internal details from the user and only showing the necessary
details to the user.
Encapsulation: It is a technique of combining the data and the member function in a single unit is known
as encapsulation.
The above figure shows the ADT model. There are two types of models in the ADT model, i.e., the
public function and the private function. The ADT model also contains the data structures that we are
using in a program. In this model, first encapsulation is performed, i.e., all the data is wrapped in a single
unit, i.e., ADT. Then, the abstraction is performed means showing the operations that can be performed
on the data structure and what are the data structures that we are using in a program.
Let's understand the abstract data type with a real-world example.
If we consider the smartphone. We look at the high specifications of the smartphone, such as:
o 4 GB RAM
o Snapdragon 2.2ghz processor
o 5 inch LCD screen
o Dual camera
o Android 8.0
The above specifications of the smartphone are the data, and we can also perform the following
operations on the smartphone:
o call(): We can call through the smartphone.
o text(): We can text a message.
o photo(): We can click a photo.
o video(): We can also make a video.
The smartphone is an entity whose data or specifications and operations are given above. The
abstract/logical view and operations are the abstract or logical views of a smartphone.
The implementation view of the above abstract/logical view is given below:
class Smartphone
{
private:
int ramSize;
string processorName;
float screenSize;
int cameraCount;
string androidVersion;
public:
void call();
void text();
void photo();
void video();
}
The above code is the implementation of the specifications and operations that can be performed on the
smartphone. The implementation view can differ because the syntax of programming languages is
different, but the abstract/logical view of the data structure would remain the same. Therefore, we can
say that the abstract/logical view is independent of the implementation view.
Note: We know the operations that can be performed on the predefined data types such as int,
float, char, etc., but we don't know the implementation details of the data types. Therefore, we
can say that the abstract data type is considered as the hidden box that hides all the internal
details of the data type.
✓ Now we’ll define three ADTs namely List ADT, Stack ADT, Queue ADT.
1. List ADT
Vies of list
• The data is generally stored in key sequence in a list which has a head structure consisting
of count, pointers and address of compare function needed to compare the data in the list.
• The data node contains the pointer to a data structure and a self-referential pointer which
points to the next node in the list.
• The List ADT Functions is given below:
• get() – Return an element from the list at any given position.
• insert() – Insert an element at any position of the list.
• remove() – Remove the first occurrence of any element from a non-empty list.
• removeAt() – Remove the element at a specified location from a non-empty list.
• replace() – Replace an element at any position by another element.
• size() – Return the number of elements in the list.
• isEmpty() – Return true if the list is empty, otherwise return false.
• isFull() – Return true if the list is full, otherwise return false.
2. Stack ADT
View of stack
• In Stack ADT Implementation instead of data being stored in each node, the pointer to data
is stored.
• The program allocates memory for the data and address is passed to the stack ADT.
• The head node and the data nodes are encapsulated in the ADT. The calling function can
only see the pointer to the stack.
• The stack head structure also contains a pointer to top and count of number of entries
currently in stack.
• push() – Insert an element at one end of the stack called top.
• pop() – Remove and return the element at the top of the stack, if it is not empty.
• peek() – Return the element at the top of the stack without removing it, if the stack is not
empty.
• size() – Return the number of elements in the stack.
• isEmpty() – Return true if the stack is empty, otherwise return false.
• isFull() – Return true if the stack is full, otherwise return false.
3. Queue ADT
View of Queue
• The queue abstract data type (ADT) follows the basic design of the stack abstract data
type.
• Each node contains a void pointer to the data and the link pointer to the next element in the
queue. The program’s responsibility is to allocate memory for storing the data.
• enqueue() – Insert an element at the end of the queue.
• dequeue() – Remove and return the first element of the queue, if the queue is not empty.
• peek() – Return the element of the queue without removing it, if the queue is not empty.
• size() – Return the number of elements in the queue.
• isEmpty() – Return true if the queue is empty, otherwise return false.
• isFull() – Return true if the queue is full, otherwise return false.
Features of ADT:
Abstract data types (ADTs) are a way of encapsulating data and operations on that data into a
single unit. Some of the key features of ADTs include:
• Abstraction: The user does not need to know the implementation of the data structure only
essentials are provided.
• Better Conceptualization: ADT gives us a better conceptualization of the real world.
• Robust: The program is robust and has the ability to catch errors.
• Encapsulation: ADTs hide the internal details of the data and provide a public interface for users
to interact with the data. This allows for easier maintenance and modification of the data structure.
• Data Abstraction: ADTs provide a level of abstraction from the implementation details of the
data. Users only need to know the operations that can be performed on the data, not how those
operations are implemented.
• Data Structure Independence: ADTs can be implemented using different data structures, such
as arrays or linked lists, without affecting the functionality of the ADT.
• Information Hiding: ADTs can protect the integrity of the data by allowing access only to
authorized users and operations. This helps prevent errors and misuse of the data.
• Modularity: ADTs can be combined with other ADTs to form larger, more complex data
structures. This allows for greater flexibility and modularity in programming.
Overall, ADTs provide a powerful tool for organizing and manipulating data in a structured and
efficient manner.
Abstract data types (ADTs) have several advantages and disadvantages that should be considered when
deciding to use them in software development. Here are some of the main advantages and
disadvantages of using ADTs:
Advantages:
• Encapsulation: ADTs provide a way to encapsulate data and operations into a single unit, making
it easier to manage and modify the data structure.
• Abstraction: ADTs allow users to work with data structures without having to know the
implementation details, which can simplify programming and reduce errors.
• Data Structure Independence: ADTs can be implemented using different data structures, which
can make it easier to adapt to changing needs and requirements.
• Information Hiding: ADTs can protect the integrity of data by controlling access and preventing
unauthorized modifications.
• Modularity: ADTs can be combined with other ADTs to form more complex data structures,
which can increase flexibility and modularity in programming.
Disadvantages:
• Overhead: Implementing ADTs can add overhead in terms of memory and processing, which can
affect performance.
• Complexity: ADTs can be complex to implement, especially for large and complex data structures.
• Learning Curve: Using ADTs requires knowledge of their implementation and usage, which can
take time and effort to learn.
• Limited Flexibility: Some ADTs may be limited in their functionality or may not be suitable for
all types of data structures.
• Cost: Implementing ADTs may require additional resources and investment, which can increase
the cost of development.
Basic Types of Data Structures
• Anything that can store data can be called as a data structure, hence Integer, Float, Boolean, Char
etc, all are data structures. They are known as Primitive Data Structures.
• Then we also have some complex Data Structures, which are used to store large and connected
data. Some example of Abstract Data Structure are :
• Linked List
• Tree
• Graph
• Stack, Queue etc.
All these data structures allow us to perform different operations on data. We select these data
structures based on which type of operation is required.
The data structures can also be classified on the basis of the following characteristics:
Characteristic Description
Linear In Linear data structures, the data items are arranged in a linear sequence. Ex: Array
Non-Linear In Non-Linear data structures, the data items are not in sequence. Ex: Tree, Graph
Homogeneous In homogeneous data structures, all the elements are of same type. Ex: Array
Non- In Non-Homogeneous data structure, the elements may or may not be of the same type.
Homogeneous Ex: Structures
Static Static data structures are those whose sizes and structures associated memory locations are
fixed, at compile time. Ex: Array
Dynamic Dynamic structures are those which expands or shrinks depending upon the program need
and its execution. Also, their associated memory locations changes. Ex: Linked List
created using pointers
Linear Data Structures: A data structure is called linear if all of its elements are arranged in the linear
order. In linear data structures, the elements are stored in non-hierarchical way where each element has
the successors and predecessors except the first and last element.
Types of Linear Data Structures are given below:
1. Arrays: The elements of array share the same variable name but each one carries a different
index number known as subscript. The array can be one dimensional, two dimensional or
multidimensional.
The individual elements of the array age are:
age[0], age[1], age[2], age[3],??? age[98], age[99].
2. Linked List: Linked list is a linear data structure which is used to maintain a list in the memory.
It can be seen as the collection of nodes stored at non-contiguous memory locations. Each node
of the list contains a pointer to its adjacent node.
3. Stack: Stack is a linear list in which insertion and deletions are allowed only at one end,
called top.
A stack is an abstract data type (ADT), can be implemented in most of the programming
languages. It is named as stack because it behaves like a real-world stack, for example: - piles of
plates or deck of cards etc.
4. Queue: Queue is a linear list in which elements can be inserted only at one end called rear and
deleted only at the other end called front.
It is an abstract data structure, similar to stack. Queue is opened at both end therefore it follows
First-In-First-Out (FIFO) methodology for storing the data items.
Non Linear Data Structures: This data structure does not form a sequence i.e. each item or element is
connected with two or more other items in a non-linear arrangement. The data elements are not arranged
in sequential structure.
Types of Non Linear Data Structures are given below:
1. Trees: Trees are multilevel data structures with a hierarchical relationship among its elements
known as nodes.The bottommost nodes in the herierchy are called leaf node while the topmost
node is called root node. Each node contains pointers to point adjacent nodes.
Tree data structure is based on the parent-child relationship among the nodes.
Each node in the tree can have more than one children except the leaf nodes whereas each node
can have atmost one parent except the root node. Trees can be classfied into many categories
which will be discussed later in this tutorial.
2. Graphs: Graphs can be defined as the pictorial representation of the set of elements (represented
by vertices) connected by the links known as edges.
A graph is different from tree in the sense that a graph can have cycle while the tree can not have
the one.
Operations on data structure
1. Traversing: Every data structure contains the set of data elements. Traversing the data structure
means visiting each element of the data structure in order to perform some specific operation like
searching or sorting.
Example: If we need to calculate the average of the marks obtained by a student in 6 different
subject, we need to traverse the complete array of marks and calculate the total sum, then we
will devide that sum by the number of subjects i.e. 6, in order to find the average.
2. Insertion: Insertion can be defined as the process of adding the elements to the data structure at
any location.
If the size of data structure is n then we can only insert n-1 data elements into it.
3. Deletion: The process of removing an element from the data structure is called Deletion. We can
delete an element from the data structure at any random location.
If we try to delete an element from an empty data structure then underflow occurs.
4. Searching: The process of finding the location of an element within the data structure is called
Searching. There are two algorithms to perform searching, Linear Search and Binary Search. We
will discuss each one of them later in this tutorial.
5. Sorting: The process of arranging the data structure in a specific order is known as Sorting.
There are many algorithms that can be used to perform sorting, for example, insertion sort,
selection sort, bubble sort, etc.
6. Merging: When two lists List A and List B of size M and N respectively, of similar type of
elements, clubbed or joined to produce the third list, List C of size (M+N), then this process is
called merging.
What is Algorithm
Definition of Algorithm
The word Algorithm means” A set of finite rules or instructions to be followed in calculations
or other problem-solving operations”
Or
” A procedure for solving a mathematical problem in a finite number of steps that frequently
involves recursive operations”.
1. Computer Science: Algorithms form the basis of computer programming and are used to
solve problems ranging from simple sorting and searching to complex tasks such as
artificial intelligence and machine learning.
2. Mathematics: Algorithms are used to solve mathematical problems, such as finding the
optimal solution to a system of linear equations or finding the shortest path in a graph.
3. Operations Research: Algorithms are used to optimize and make decisions in fields such
as transportation, logistics, and resource allocation.
4. Artificial Intelligence: Algorithms are the foundation of artificial intelligence and
machine learning, and are used to develop intelligent systems that can perform tasks such
as image recognition, natural language processing, and decision-making.
5. Data Science: Algorithms are used to analyze, process, and extract insights from large
amounts of data in fields such as marketing, finance, and healthcare.
These are just a few examples of the many applications of algorithms. The use of algorithms
is continually expanding as new technologies and fields emerge, making it a vital component
of modern society.
Algorithms can be simple and complex depending on what you want to achieve.
It can be understood by taking the example of cooking a new recipe. To cook a new recipe,
one reads the instructions and steps and executes them one by one, in the given sequence. The
result thus obtained is the new dish is cooked perfectly. Every time you use your phone,
computer, laptop, or calculator you are using Algorithms. Similarly, algorithms help to do a
task in programming to get the expected output.
The Algorithm designed are language-independent, i.e. they are just plain instructions that can
be implemented in any language, and yet the output will be the same, as expected.
As one would not follow any written instructions to cook the recipe, but only the standard one.
Similarly, not all written instructions for programming are an algorithm.
Dataflow of an Algorithm
• Problem: A problem can be a real-world problem or any instance from the real-world
problem for which we need to create a program or the set of instructions. The set of
instructions is known as an algorithm.
• Algorithm: An algorithm will be designed for a problem which is a step by step procedure.
• Input: After designing an algorithm, the required and the desired inputs are provided to the
algorithm.
• Processing unit: The input will be given to the processing unit, and the processing unit will
produce the desired output.
• Output: The output is the outcome or the result of the program.
Types of Algorithms:
There are several types of algorithms available. Some important algorithms are:
1. Brute Force Algorithm:
It is the simplest approach to a problem. A brute force algorithm is the first approach that
comes to finding when we see a problem.
2. Recursive Algorithm:
A recursive algorithm is based on recursion. In this case, a problem is broken into several sub-
parts and called the same function again and again.
3. Backtracking Algorithm:
The backtracking algorithm builds the solution by searching among all possible solutions.
Using this algorithm, we keep on building the solution following criteria. Whenever a solution
fails we trace back to the failure point build on the next solution and continue this process till
we find the solution or all possible solutions are looked after.
4. Searching Algorithm:
Searching algorithms are the ones that are used for searching elements or groups of elements
from a particular data structure. They can be of different types based on their approach or the
data structure in which the element should be found.
5. Sorting Algorithm:
Sorting is arranging a group of data in a particular manner according to the requirement. The
algorithms which help in performing this function are called sorting algorithms. Generally
sorting algorithms are used to sort groups of data in an increasing or decreasing manner.
6. Hashing Algorithm:
Hashing algorithms work similarly to the searching algorithm. But they contain an index with
a key ID. In hashing, a key is assigned to specific data.
This algorithm breaks a problem into sub-problems, solves a single sub-problem, and merges
the solutions to get the final solution. It consists of the following three steps:
• Divide
• Solve
• Combine
8. Greedy Algorithm:
In this type of algorithm, the solution is built part by part. The solution for the next part is built
based on the immediate benefit of the next part. The one solution that gives the most benefit
will be chosen as the solution for the next part.
This algorithm uses the concept of using the already found solution to avoid repetitive
calculation of the same part of the problem. It divides the problem into smaller overlapping
subproblems and solves them.
In the randomized algorithm, we use a random number so it gives immediate benefit. The
random number helps in deciding the expected outcome.
Advantages of Algorithms:
• It is easy to understand.
• An algorithm is a step-wise representation of a solution to a given problem.
• In an Algorithm the problem is broken down into smaller pieces or steps hence, it is easier
for the programmer to convert it into an actual program.
Disadvantages of Algorithms:
• Writing an algorithm takes a long time so it is time-consuming.
• Understanding complex logic through algorithms can be very difficult.
• Branching and Looping statements are difficult to show in Algorithms (imp).
How to Design an Algorithm?
To write an algorithm, the following things are needed as a pre-requisite:
1. The problem that is to be solved by this algorithm i.e. clear problem definition.
2. The constraints of the problem must be considered while solving the problem.
3. The input to be taken to solve the problem.
4. The output is to be expected when the problem is solved.
5. The solution to this problem is within the given constraints.
Then the algorithm is written with the help of the above parameters such that it solves the problem.
Example: Consider the example to add three numbers and print the sum.
#include <stdio.h>
int main()
{
return 0;
}
Algorithm Analysis
As we know that data structure is a way of organizing the data efficiently and that efficiency is
measured either in terms of time or space. So, the ideal data structure is a structure that occupies the
least possible time to perform all its operation and the memory space. Our focus would be on finding
the time complexity rather than space complexity, and by finding the time complexity; we can decide
which data structure is the best for an algorithm.
The main question arises in our mind that on what basis should we compare the time complexity of
data structures? The time complexity can be compared based on operations performed on them.
Let's consider a simple example.
Suppose we have an array of 100 elements, and we want to insert a new element at the beginning of
the array. This becomes a very tedious task as we first need to shift the elements towards the right,
and we will add new element at the starting of the array.
Suppose we consider the linked list as a data structure to add the element at the beginning. The linked
list contains two parts, i.e., data and address of the next node. We simply add the address of the first
node in the new node, and head pointer will now point to the newly added node.
Therefore, we conclude that adding the data at the beginning of the linked list is faster than the arrays.
In this way, we can compare the data structures and select the best possible data structure for
performing the operations.
Algorithm analysis is an important part of computational complexity theory, which provides
theoretical estimation for the required resources of an algorithm to solve a specific computational
problem. Analysis of algorithms is the determination of the amount of time and space resources
required to execute it.
1. Best case
2. Worst case
3. Average case
• Best case: Define the input for which algorithm takes less time or minimum time. In the
best case calculate the lower bound of an algorithm.
Example: In the linear search when search data is present at the first location of large data
then the best case occurs.
• Worst Case: Define the input for which algorithm takes a long time or maximum time. In
the worst calculate the upper bound of an algorithm.
Example: In the linear search when search data is not present at all then the worst case
occurs.
• Average case: In the average case take all random inputs and calculate the computation
time for all inputs.And then we divide it by the total number of inputs.
Average case = all random case time / total no of case
Space-Time tradeoff
• A space-time or time-memory tradeoff is a way of solving in less time by using more
storage space or by solving a given algorithm in very little space by spending more time.
• To solve a given programming problem, many different algorithms may be used. Some
of these algorithms may be extremely time-efficient and others extremely space-
efficient.
• Time/space trade off refers to a situation where you can reduce the use of memory at
the cost of slower program execution, or reduce the running time at the cost of increased
memory usage.
How to find the Time Complexity or running time for performing the
operations?
The measuring of the actual running time is not practical at all. The running time to perform any
operation depends on the size of the input.
Let's understand this statement through a simple example.
Suppose we have an array of five elements, and we want to add a new element at the beginning of the
array. To achieve this, we need to shift each element towards right, and suppose each element takes one
unit of time. There are five elements, so five units of time would be taken. Suppose there are 1000
elements in an array, then it takes 1000 units of time to shift. It concludes that time complexity depends
upon the input size.
Therefore, if the input size is n, then f(n) is a function of n that denotes the time complexity.
How to calculate f(n)?
Calculating the value of f(n) for smaller programs is easy but for bigger programs, it's not that easy. We
can compare the data structures by comparing their f(n) values. We can compare the data structures by
comparing their f(n) values. We will find the growth rate of f(n) because there might be a possibility that
one data structure for a smaller input size is better than the other one but not for the larger sizes. Now,
how to find f(n).
Let's look at a simple example.
f(n) = 5n2 + 6n + 12
where n is the number of instructions executed, and it depends on the size of the input.
When n=1
As we can observe in the above table that with the increase in the value of n, the running time of
5n2 increases while the running time of 6n and 12 also decreases. Therefore, it is observed that for larger
values of n, the squared term consumes almost 99% of the time. As the n 2 term is contributing most of
the time, so we can eliminate the rest two terms.
Therefore,
f(n) = 5n2
Here, we are getting the approximate time complexity whose result is very close to the actual result. And
this approximate measure of time complexity is known as an Asymptotic complexity. Here, we are not
calculating the exact running time, we are eliminating the unnecessary terms, and we are just considering
the term which is taking most of the time.
In mathematical analysis, asymptotic analysis of algorithm is a method of defining the mathematical
boundation of its run-time performance. Using the asymptotic analysis, we can easily conclude the
average-case, best-case and worst-case scenario of an algorithm.
It is used to mathematically calculate the running time of any operation inside an algorithm.
Example: Running time of one operation is x(n) and for another operation, it is calculated as f(n2). It
refers to running time will increase linearly with an increase in 'n' for the first operation, and running
time will increase exponentially for the second operation. Similarly, the running time of both operations
will be the same if n is significantly small.
Usually, the time required by an algorithm comes under three types:
Worst case: It defines the input for which the algorithm takes a huge time.
Average case: It takes average time for the program execution.
Best case: It defines the input for which the algorithm takes the lowest time
Asymptotic Notations
What is Asymptotic Notation?
Whenever we want to perform analysis of an algorithm, we need to calculate the complexity of that algorithm. But
when we calculate the complexity of an algorithm it does not provide the exact amount of resource required. So instead
of taking the exact amount of resource, we represent that complexity in a general form (Notation) which produces the
basic nature of that algorithm. We use that general form (Notation) for analysis process.
Asymptotic notation of an algorithm is a mathematical representation of its complexity.
Note - In asymptotic notation, when we want to represent the complexity of an algorithm, we use only the most
significant terms in the complexity of that algorithm and ignore least significant terms in the complexity of that
• Algorithm 1 : 5n2 + 2n + 1
• Algorithm 2 : 10n2 + 8n + 3
Generally, when we analyze an algorithm, we consider the time complexity for larger values of input data
(i.e. 'n' value). In above two time complexities, for larger value of 'n' the term '2n + 1' in algorithm 1 has least
significance than the term '5n2', and the term '8n + 3' in algorithm 2 has least significance than the term '10n2'.
Here, for larger value of 'n' the value of most significant terms ( 5n2 and 10n2 ) is very larger than the value of least
significant terms ( 2n + 1 and 8n + 3 ). So for larger value of 'n' we ignore the least significant terms to represent
overall time required by an algorithm. In asymptotic notation, we use only the most significant terms to represent the
Majorly, we use THREE types of Asymptotic Notations and those are as follows...
1. Big - Oh (O)
2. Omega (Ω)
3. Theta (Θ)
Big - Oh Notation (O)
Big - Oh notation is used to define the upper bound of an algorithm in terms of Time Complexity.
That means Big - Oh notation always indicates the maximum time required by an algorithm for all input values. That
means Big - Oh notation describes the worst case of an algorithm time complexity.
Consider function f(n) as time complexity of an algorithm and g(n) is the most significant term. If f(n) <= C
g(n) for all n >= n0, C > 0 and n0 >= 1. Then we can represent f(n) as O(g(n)).
f(n) = O(g(n))
Consider the following graph drawn for the values of f(n) and C g(n) for input (n) value on X-Axis and time required
is on Y-Axis
In above graph after a particular input value n0, always C g(n) is greater than f(n) which indicates the algorithm's upper
bound.
Example
f(n) = 3n + 2
g(n) = n
If we want to represent f(n) as O(g(n)) then it must satisfy f(n) <= C g(n) for all values of C > 0 and n0>= 1
⇒3n + 2 <= C n
3n + 2 = O(n)
Omege Notation (Ω)
Omega notation is used to define the lower bound of an algorithm in terms of Time Complexity.
That means Omega notation always indicates the minimum time required by an algorithm for all input values. That
means Omega notation describes the best case of an algorithm time complexity.
Consider function f(n) as time complexity of an algorithm and g(n) is the most significant term. If f(n) >= C
g(n) for all n >= n0, C > 0 and n0 >= 1. Then we can represent f(n) as Ω(g(n)).
f(n) = Ω(g(n))
Consider the following graph drawn for the values of f(n) and C g(n) for input (n) value on X-Axis and time required
is on Y-Axis
In above graph after a particular input value n0, always C g(n) is less than f(n) which indicates the algorithm's lower
bound.
Example
f(n) = 3n + 2
g(n) = n
If we want to represent f(n) as Ω(g(n)) then it must satisfy f(n) >= C g(n) for all values of C > 0 and n0>= 1
⇒3n + 2 >= C n
3n + 2 = Ω(n)
Theta Notation (Θ)
Theta notation is used to define the average bound of an algorithm in terms of Time Complexity.
That means Theta notation always indicates the average time required by an algorithm for all input values. That means
Theta notation describes the average case of an algorithm time complexity.
Consider function f(n) as time complexity of an algorithm and g(n) is the most significant term. If C1 g(n) <=
f(n) <= C2 g(n) for all n >= n0, C1 > 0, C2 > 0 and n0 >= 1. Then we can represent f(n) as Θ(g(n)).
f(n) = Θ(g(n))
Consider the following graph drawn for the values of f(n) and C g(n) for input (n) value on X-Axis and time required
is on Y-Axis
In above graph after a particular input value n0, always C1 g(n) is less than f(n) and C2 g(n) is greater than f(n) which
Example
f(n) = 3n + 2
g(n) = n
If we want to represent f(n) as Θ(g(n)) then it must satisfy C1 g(n) <= f(n) <= C2 g(n) for all values of C1 > 0, C2 >
0 and n0>= 1
3n + 2 = Θ(n)
Arrays
What is an Array?
Whenever we want to work with large number of data values, we need to use that much number of different variables.
As the number of variables are increasing, complexity of the program also increases and programmers get confused
with the variable names. There may be situations in which we need to work with large number of similar data values.
To make this work more easy, C programming language provides a concept called "Array".
An array is a variable which can store multiple values of same data type at a time.
"Collection of similar data items stored in continuous memory locations with single name".
Operations of Array
TRAVERSING
INSERTION
DELETION
Algorithm for Deleting the kth element from a linear array
MERGING
INVERSION
Example:
Given the base address of an array A[1300 ………… 1900] as 1020 and the size of each element is 2
bytes in the memory, find the address of A[1700].
Solution:
Given:
Base address B = 1020
Lower Limit/Lower Bound of subscript LB = 1300
Storage size of one element store in any array W = 2 Byte
Subset of element whose address to be found I = 1700
Formula used:
Address of A[I] = B + W * (I – LB)
Solution:
Address of A[1700] = 1020 + 2 * (1700 – 1300)
= 1020 + 2 * (400)
= 1020 + 800
Address of A[1700] = 1820
Example: Given an array, arr[1………10][1………15] with base value 100 and the size of each
element is 1 Byte in memory. Find the address of arr[8][6] with the help of row-major order.
Solution:
Given:
Base address B = 100
Storage size of one element store in any array W = 1 Bytes
Row Subset of an element whose address to be found I = 8
Column Subset of an element whose address to be found J = 6
Lower Limit of row/start row index of matrix LR = 1
Lower Limit of column/start column index of matrix = 1
Number of column given in the matrix N = Upper Bound – Lower Bound + 1
= 15 – 1 + 1
= 15
Formula:
Address of A[I][J] = B + W * ((I – LR) * N + (J – LC))
Solution:
Address of A[8][6] = 100 + 1 * ((8 – 1) * 15 + (6 – 1))
= 100 + 1 * ((7) * 15 + (5))
= 100 + 1 * (110)
Address of A[I][J] = 210
A 2-D array A[4....7, -1....3] requires 2 bytes of storage space for each element. If the array is stored in
row-major from having base address 100, then the address of A[6, 2] will be _____ Read more on
Sarthaks.com - https://www.sarthaks.com/2566527/array-requires-bytes-storage-space-each-element-
array-stored-major-from-having-base-address
Ans: 126
A[4....7, -1....3] = A[lower bound of row..........upper bound of the row, lower bound of column.......
upper bound of column]
The formula to find the base address of elements of the array stored in row-major order is:
Address(a[i][j]) = B. A. + ((i - lower bound given for row) * n + (j - lower bound given for
column)) * size
here, i=6 and j=2
lower bound for row = 4
lower bound for column = -1
Number of rows:- 7- 4 + 1= 4
Number of column:- 3 -(-1) + 1 = 5
Assumptions:
• The N-Dimensional array A with the maximum sizes of N dimensions are S1, S2, S3, ………, SN.
• The element whose address needs to be calculated has indices l1, l2, l3,……… …..lN respectively.
• It is possible that the array indices do not have the lower bound as zero.
For example, consider the following array A: A[-5…5][2……9][14…54][-9…-2].
Explanation:
• In the above array A, the lower bounds of indices are not zeroes.
• So that the sizes of the indices of the array are different now and can be calculated by using the
formula:
UpperBound – LowerBound +1
• So, here the S1 = 5 – (-5) + 1 = 11. Similarly, S2 = 8, S3 = 41 and S4 = 8.
For address calculation, the lower bounds are t1, t2, t3…….tN.
There exist two ways to store the array elements:
• Row Major
• Column Major
***NOTE***
When we move to 3D and beyond, it's best to leave the row/column notation of matrices behind. This
is because this notation doesn't easily translate to 3 dimensions due to a common confusion between
rows, columns and the Cartesian coordinate system.
In 4 dimensions and above, we lose any purely-visual intuition to describe multi-dimensional entities
anyway, so it's best to stick to a consistent mathematical notation instead.
The Row Major formula:
Address of A[I1][I2]. . . .[lN] = BA + W*[((E1S2 + E2 )S3 +E3 )S4 ….. + EN-1 )SN + EN]
***The approach is similar for the Column Major but the pattern of the interior
sequence is reverse of the row-major pattern. ***
The Column Major formula:
Address of A[I1][I2]. . . [IN] = BA + W*[((…ENSN-1+ EN-1 )SN-2 +… E3 )S2+ E2 )S1 +E1]
• BA represents the base address of the array.
• W is Storage Size of one element stored in the array (in byte).
• Also, Ei is given by Ei = li – ti, where li and ti are the calculated indexes (indices of array element
which needs to be determined) and lower bounds respectively.
The simplest way to learn the formulas:
For row-major: If width = 5, the interior sequence is E1S2 + E2S3 + E3S4 + E4S5 + E5 and if width = 3,
the interior sequence is E1S2 + E2S3 + E3.
Figure out the pattern in the order and follow four basic steps for the formula of any width:
• Write the interior sequence.
• Put the closing bracket after each E except the first term. So for width = 5, it becomes
E1S2 + E2) S3 + E3) S4 + E4) S5 + E5).
• Put all the Opening brackets initially.
((((E1S2 + E2)S3 + E3)S4 + E4)S5 + E5).
• Incorporate the base address and width in the formula.
For column-major: If width = 5, the interior sequence is E5S4 + E4S3 + E3S2+ E2S1 + E1.
Example: Let’s take a multi-dimensional array A [10][20][30][40] with the base address 1200. The task
is to find the address of element A [1][3][5][6].
By applying the formula for row-major, directly write the formula as:
A[1][3][5][6] = 1200 + 4(((1 × 20 + 3)30 +5) 40 + 6)
=1200 +4((23 × 30 +5)40 +6)
=1200 + 4(695 × 40 + 6)
=1200 + (4 × 27806)
=112424.
Other way to calculate the address of any element in the 3-D Array:
A 3-Dimensional array is a collection of 2-Dimensional arrays. It is specified by using three
subscripts:
1. Block size
2. Row size
3. Column size
More dimensions in an array mean more data can be stored in that array.
Example:
3-D array
To find the address of any element in 3-Dimensional arrays there are the following two ways-
• Row Major Order
• Column Major Order
1. Row Major Order:
To find the address of the element using row-major order, use the following formula:
Address of A[i][j][k] = B + W *(M * N * (i-x) + N *(j-y) + (k-z))
Here:
B = Base Address (start address)
W = Weight (storage size of one element stored in the array)
M = Row (total number of rows)
N = Column (total number of columns)
P = Width (total number of cells depth-wise)
x = Lower Bound of Row
y = Lower Bound of Column
z = Lower Bound of Width
Example: Given an array, arr[1:9, -4:1, 5:10] with a base value of 400 and the size of each element is
2 Bytes in memory find the address of element arr[5][-1][8] with the help of row-major order?
Solution:
Given:
Block Subset of an element whose address to be found I = 5
Row Subset of an element whose address to be found J = -1
Column Subset of an element whose address to be found K = 8
Base address B = 400
Storage size of one element store in any array(in Byte) W = 2
Lower Limit of blocks in matrix x = 1
Lower Limit of row/start row index of matrix y = -4
Lower Limit of column/start column index of matrix z = 5
M(row) = Upper Bound – Lower Bound + 1 = 1 – (-4) + 1 = 6
N(Column)= Upper Bound – Lower Bound + 1 = 10 – 5 + 1 = 6
Formula used:
Address of[I][J][K] =B + W (M * N(i-x) + N *(j-y) + (k-z))
Solution:
Address of arr[5][-1][8] = 400 + 2 * {[6 * 6 * (5 – 1)] + 6 * [(-1 + 4)]} + [8 – 5]
= 400 + 2 * (6*6*4)+(6*3)+3
= 400 + 2 * (165)
= 730
2. Column Major Order:
To find the address of the element using column-major order, use the following formula:1
Address of A[i][j][k]= B + W(M * N(i – x) + M *(k – z) + (j – y))
Here:
B = Base Address (start address)
W = Weight (storage size of one element stored in the array)
M = Row (total number of rows)
N = Column (total number of columns)
P = Width (total number of cells depth-wise)
x = Lower Bound of block (first subscipt)
y = Lower Bound of Row
z = Lower Bound of Column
Example:
Consider a multidimensional array A[90][30][40] with base address started 1000. Calculate the
address of A[10][20][30] in row major order and column major order. Assume the first element
stored at A[2][2][2] and each element take 2 bytes.
Sol:
Given, A [90] [30] [40]
B = 1000 , W= 2 bytes
First element stored at A [2] [2] [2]
LOC (A[10][20][30]) = ?
Sol:
L1=UB1-LB1+1 = 90-2+1 = 91
L2=UB2-LB2+1 = 30-2+1 = 31
L3=UB3-LB3+1 = 40-2+1 = 41
E1=K1-LB1 = 10-2 = 8
E2=K2-LB2 = 20-2 = 18
E3=K3-LB3 = 30-2 = 28
In row major order
LOC(A[k1,k2,k3]) = B + w ((E1*L2+E2)*L3+E3)
LOC(A[10][20][30]) = 1000 + 2 ( (8*31+18)*41+28)
= 22868
Another way to find LOC(A[10][20][30]) In row major order
A[90][30][40]
A[L,M,N]
first element stored at A[2][2][2]
LOC(A[10][20][30])=?
L (No. of Blocks) = Upper Bound – Lower Bound + 1 = 90-2 + 1 = 91
M (No. of rows) = Upper Bound – Lower Bound + 1 = 30 –2 + 1 = 31
N (No. of columns) = Upper Bound – Lower Bound + 1 = 40– 2 + 1 = 41
In column major
LOC(A[k1,k2,k3]) = B+w ((E3*L2+E2)*L1+E1)
= 1000 + 2((28*31+18)*91+8)
= 162268
*** The approach is similar for the Column Major but the pattern of the interior
sequence is reverse of the row-major pattern.
Another way to find LOC (A[10][20][30])
In Column major order
A[90][30][40]
A[L,M,N]
First element stored at A[2][2][2]
LOC(A[10][20][30])=?
L (No. of rows) = Upper Bound – Lower Bound + 1 = 90-2 + 1 = 91
M (No. of columns) = Upper Bound – Lower Bound + 1 = 30 –2 + 1 = 31
N (No. of Blocks) = Upper Bound – Lower Bound + 1 = 40– 2 + 1 = 41
LOC(Q[3,3,3])= B+W((E3*Lq2+E2)*Lq1+E1)
= 400+((13*11+8)*8+2)
= 5240
Solution 2: Q[1:8,-5:5,-10:5]
Q[L,M,N]
LOC(Q[3,3,3])=?
L (No. of rows) = Upper Bound – Lower Bound + 1 = 8-1 + 1 =8
M (No. of columns) = Upper Bound – Lower Bound + 1 = 5 – (-5) + 1 = 11
N (No. of Blocks) = Upper Bound – Lower Bound + 1 = 5 – (-10) + 1 = 16
Linked List
If arrays accommodate similar types of data types, linked lists consist of elements with different data
types that are also arranged sequentially.
Why use linked list over array?
Till now, we were using array data structure to organize the group of elements that are to be stored
individually in the memory. However, Array has several advantages and disadvantages which must be
known in order to decide the data structure which will be used throughout the program.
Linked list is the data structure which can overcome all the limitations of an array.
Using linked list is useful because,
1. It allocates the memory dynamically. All the nodes of linked list are non-contiguously stored in
the memory and linked together with the help of pointers.
2. Sizing is no longer a problem since we do not need to define its size at the time of declaration.
List grows as per the program's demand and limited to the available memory space.
Definition
A linked list or one way list is an ordered collection of finite data elements called nodes where the linear
order is maintained by means of links or pointers.
Each node is divided into two parts: the first part contains the information of the element and the second
part called the linked link field on next pointer field contains the address of the next node in the list.
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.
Imagine that we are inserting a node B (NewNode), between A (LeftNode) and C (RightNode). Then
point B.next to C −
B −> next = C;
ie B −> next = A −> next
Now, the next node at the left should point to the new node.
A −> next = B;
This will put the new node in the middle of the two. The new list should look like this −
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
Algorithm
1. IF PTR = NULL
Write OVERFLOW
Go to Step 7
[END OF IF]
2. SET NEW_NODE = PTR
3. SET PTR = PTR → NEXT
4. SET NEW_NODE → DATA = VAL
5. SET NEW_NODE → NEXT = HEAD
6. SET HEAD = NEW_NODE
7. EXIT
Insertion at Ending
In this operation, we are adding an element at the ending of the list.
In order to insert a node at the last, there are two following scenarios which need to be mentioned.
Algorithm
1. START
2. Create a new node and assign the data
3. Find the last node
4. Point the last node to new node
5. END
Algorithm
1. IF PTR = NULL Write OVERFLOW
Go to Step 1
[END OF IF]
2. SET NEW_NODE = PTR
3. SET PTR = PTR - > NEXT
4. SET NEW_NODE - > DATA = VAL
5. SET NEW_NODE - > NEXT = NULL
6. SET PTR = HEAD
7. Repeat Step 8 while PTR - > NEXT != NULL
8. SET PTR = PTR - > NEXT
[END OF LOOP]
9. SET PTR - > NEXT = NEW_NODE
10. EXIT
Algorithm
STEP 1: IF AVAIL = NULL
WRITE OVERFLOW
GOTO STEP 10
END OF IF
STEP 2: SET NEW_NODE = AVAIL
STEP 3: NEW_NODE → DATA = VAL
STEP 4: SET PTR = HEAD
STEP 5: REPEAT STEP 6 WHILE PTR → DATA != ITEM
STEP 6: PTR=PRT→NEXT
STEP 7: IF TEMP = NULL
WRITE "DESIRED NODE NOT PRESENT"
GOTO STEP 10
END OF IF
END OF LOOP
STEP 8: NEW_NODE → NEXT = PTR → NEXT
STEP 9: PTR → NEXT = NEW_NODE
STEP 10: EXIT
Traversal Operation
• The traversal operation walks through all the elements of the list in an order and displays the
elements in that order.
• Traversing means visiting each node of the list once in order to perform some operation on that.
Algorithm
1. START
2. While the list is not empty and did not reach the end of the list, print the data in each node
3. END
Algorithm
1. SET PTR = HEAD
2. IF PTR = NULL
[END OF LOOP]
6. EXIT
Search Operation
• Searching for an element in the list using a key element.
• This operation is done in the same way as array search; comparing every element in the list
with the key element given.
• Searching is performed in order to find the location of a particular element in the list.
• Searching any element in the list needs traversing through the list and make the comparison of
every element of the list with the specified element. If the element is matched with any of the list
element then the location of the element is returned from the function.
Algorithm
1 START
2 If the list is not empty, iteratively check if the list contains the key
3 If the key element is not present in the list, unsuccessful search
4 END
Algorithm
1. SET PTR = HEAD
2. Set I = 0
3. IF PTR = NULL
write I+1
End of IF
6. I = I + 1
[END OF LOOP]
8. EXIT
Deletion Operation
Deletion is also a more than one step process.
First, locate the target node to be removed, by using searching algorithms.
The left (previous) node of the target node now should point to the next node of the target node −
LeftNode−>next = TargetNode−>next;
This will remove the link that was pointing to the target node. Now, using the following code, we will remove
what the target node is pointing at.
TargetNode.next −> NULL;
We need to use the deleted node. We can keep that in memory otherwise we can simply deallocate memory and
wipe off the target node completely.
free(TargetNode)
Deletion in linked lists is also performed in three different ways. They are as
follows −
Deletion at Beginning
In this deletion operation of the linked, we are deleting an element from the beginning of the list. For this, we
point the head to the second node.
Algorithm
1. START
2. Assign the head pointer to the next node in the list
3. END
Algorithm
Step 1: IF HEAD = NULL
Write UNDERFLOW
Go to Step 5
[END OF IF]
Deletion at Ending
In this deletion operation of the linked, we are deleting an element from the ending of the list.
There are two scenarios in which, a node is deleted from the end of the linked list.
1. There is only one node in the list and that needs to be deleted.
2. There are more than one node in the list and the last node of the list will be deleted.
Algorithm
1. START
2. Iterate until you find the second last element in the list.
3. Assign NULL to the second last element in the list.
4. END
Algorithm
Step 1: IF HEAD = NULL
Write UNDERFLOW
Go to Step 8
[END OF IF]
[END OF LOOP]
Step 8: EXIT
Deletion at a Given Position
In this deletion operation of the linked, we are deleting an element at any position of the list.
Algorithm
1. START
3. Assign the adjacent node of current node in the list to its previous node.
4. END
Algorithm
Step 1 - Check whether list is Empty (head == NULL)
Step 2 - If it is Empty then, display 'List is Empty!!! Deletion is not possible' and terminate the function.
Step 3 - If it is Not Empty then, define two Node pointers 'temp1' and 'temp2' and initialize 'temp1' with head.
Step 4 - Check whether list has only one Node (temp1 → next == NULL)
Step 5 - If it is TRUE. Then, set head = NULL and delete temp1. And terminate the function. (Setting Empty list
condition)
Step 6 - If it is FALSE. Then, set 'temp2 = temp1 ' and move temp1 to its next node. Repeat the same until it
reaches to the last node in the list. (until temp1 → next == NULL)
Step 7 - Finally, Set temp2 → next = NULL and delete temp1.
Reverse Operation
This operation is a thorough one. We need to make the last node to be pointed by the head node and reverse the
whole linked list.
First, we traverse to the end of the list. It should be pointing to NULL. Now, we shall make it point to its
previous node −
We have to make sure that the last node is not the last node. So we'll have some temp node, which looks like the
head node pointing to the last node. Now, we shall make all left side nodes point to their previous nodes one
by one.
Except the node (first node) pointed by the head node, all nodes should point to their predecessor, making them
their new successor. The first node will point to NULL.
We'll make the head node point to the new first node by using the temp node.
Algorithm
Step by step process to reverse a linked list is as follows −
Step 1 - Check whether list is Empty (head == NULL)
Step 2 - If it is Empty then, display 'List is Empty!!! Deletion is not possible' and terminate the function.
Step 3 - If it is Not Empty then, define two Node pointers 'temp1' and 'temp2' and initialize 'temp1' with head.
Step 4 - Keep moving the temp1 until it reaches to the exact node to be deleted or to the last node. And every time
set 'temp2 = temp1' before moving the 'temp1' to its next node.
Step 5 - If it is reached to the last node then display 'Given node not found in the list! Deletion not possible!!!'.
And terminate the function.
Step 6 - If it is reached to the exact node which we want to delete, then check whether list is having only one node
or not
Step 7 - If list has only one node and that is the node to be deleted, then set head = NULL and
delete temp1 (free(temp1)).
Step 8 - If list contains multiple nodes, then check whether temp1 is the first node in the list (temp1 == head).
Step 9 - If temp1 is the first node then move the head to the next node (head = head → next) and delete temp1.
Step 10 - If temp1 is not first node then check whether it is last node in the list (temp1 → next == NULL).
Step 11 - If temp1 is last node then set temp2 → next = NULL and delete temp1 (free(temp1)).
Step 12 - If temp1 is not first node and not last node then set temp2 → next = temp1 → next and
delete temp1 (free(temp1)).
Example
Following are the implementations of this operation in C −
#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
void insertAtBeginning(int);
void insertAtEnd(int);
void insertBetween(int,int,int);
void display();
void removeBeginning();
void removeEnd();
void removeSpecific(int);
struct Node
{
int data;
struct Node *next;
}*head = NULL;
void main()
{
int choice,value,choice1,loc1,loc2;
clrscr();
while(1){
mainMenu: printf("\n\n****** MENU ******\n1. Insert\n2. Display\n3. Delete\n4. Exit\nEnter your choice: ");
scanf("%d",&choice);
switch(choice)
{
case 1: printf("Enter the value to be insert: ");
scanf("%d",&value);
while(1){
printf("Where you want to insert: \n1. At Beginning\n2. At End\n3. Between\nEnter your choice: ");
scanf("%d",&choice1);
switch(choice1)
{
case 1: insertAtBeginning(value);
break;
case 2: insertAtEnd(value);
break;
case 3: printf("Enter the two values where you wanto insert: ");
scanf("%d%d",&loc1,&loc2);
insertBetween(value,loc1,loc2);
break;
default: printf("\nWrong Input!! Try again!!!\n\n");
goto mainMenu;
}
goto subMenuEnd;
}
subMenuEnd:
break;
case 2: display();
break;
case 3: printf("How do you want to Delete: \n1. From Beginning\n2. From End\n3. Spesific\nEnter your choice: ");
scanf("%d",&choice1);
switch(choice1)
{
case 1: removeBeginning();
break;
case 2: removeEnd();
break;
case 3: printf("Enter the value which you wanto delete: ");
scanf("%d",&loc2);
removeSpecific(loc2);
break;
default: printf("\nWrong Input!! Try again!!!\n\n");
goto mainMenu;
}
break;
case 4: exit(0);
default: printf("\nWrong input!!! Try again!!\n\n");
}
}
}
• Doubly linked list is a complex type of linked list in which a node contains a pointer to the
previous as well as the next node in the sequence.
• Therefore, in a doubly linked list, a node consists of three parts:
node data,
pointer to the next node in sequence (next pointer),
pointer to the previous node (previous pointer).
• A sample node in a doubly linked list is shown in the figure.
• A doubly linked list containing three nodes having numbers from 1 to 3 in their data part, is
shown in the following image.
Basic Operations
Following are the basic operations supported by a list.
• Insertion − Adds an element at the beginning of the list.
• Deletion − Deletes an element at the beginning of the list.
• Insert Last − Adds an element at the end of the list.
• Delete Last − Deletes an element from the end of the list.
• Insert After − Adds an element after an item of the list.
• Delete − Deletes an element from the list using the key.
• Display forward − Displays the complete list in a forward manner.
• Display backward − Displays the complete list in a backward manner.
5. Otherwise, link the address of the existing first node to the next variable of the new node, and
assign null to the prev variable.
Algorithm :
Step 1: IF ptr = NULL
Write OVERFLOW
Go to Step 9
[END OF IF]
Step 9: EXIT
2. If the list is empty, add the node to the list and point the head to it.
3. If the list is not empty, find the last node of the list.
4. Create a link between the last node in the list and the new node.
5. The new node will point to NULL as it is the new last node.
6. END
Algorithm :
Step 1: IF PTR = NULL
Write OVERFLOW
Go to Step 11
[END OF IF]
[END OF LOOP]
WRITE "UNDERFLOW"
GOTO STEP 6
[END OF IF]
Step 6: Exit
WRITE "UNDERFLOW"
GOTO STEP 8
[END OF IF]
Step 3: Set i = 0
Step 4: Repeat step 5 to 7 while PTR != NULL
return i
[END OF IF]
Step 6: i = i + 1
Step 8: Exit
4. If the list is not empty, the head pointer is shifted to the next node.
5. END
Algorithm
STEP 1: IF HEAD = NULL
WRITE UNDERFLOW
GOTO STEP 6
STEP 6: EXIT
Algorithm
Step 1: IF HEAD = NULL
Write UNDERFLOW
Go to Step 7
[END OF IF]
[END OF LOOP]
Step 7: EXIT
Deleting a Specific Node from the list
Algorithm
Step 1 - Check whether list is Empty (head == NULL)
Step 2 - If it is Empty then, display 'List is Empty!!! Deletion is not possible' and terminate the function.
Step 3 - If it is not Empty, then define a Node pointer 'temp' and initialize with head.
Step 4 - Keep moving the temp until it reaches to the exact node to be deleted or to the last node.
Step 5 - If it is reached to the last node, then display 'Given node not found in the list! Deletion not
possible!!!' and terminate the fuction.
Step 6 - If it is reached to the exact node which we want to delete, then check whether list is having only one node
or not
Step 7 - If list has only one node and that is the node which is to be deleted then set head to NULL and
delete temp (free(temp)).
Step 8 - If list contains multiple nodes, then check whether temp is the first node in the list (temp == head).
Step 9 - If temp is the first node, then move the head to the next node (head = head → next),
set head of previous to NULL (head → previous = NULL) and delete temp.
Step 10 - If temp is not the first node, then check whether it is the last node in the list (temp → next == NULL).
Step 11 - If temp is the last node then set temp of previous of next to NULL (temp → previous → next =
NULL) and delete temp (free(temp)).
Step 12 - If temp is not the first node and not the last node, then
set temp of previous of next to temp of next (temp → previous → next = temp →
next), temp of next of previous to temp of previous (temp → next → previous = temp → previous) and
delete temp (free(temp)).
Example
Following are the implementations of this operation in C −
#include<stdio.h>
#include<conio.h>
void insertAtBeginning(int);
void insertAtEnd(int);
void insertAtAfter(int,int);
void deleteBeginning();
void deleteEnd();
void deleteSpecific(int);
void display();
struct Node
{
int data;
struct Node *previous, *next;
}*head = NULL;
void main()
{
int choice1, choice2, value, location;
clrscr();
while(1)
{
printf("\n*********** MENU *************\n");
printf("1. Insert\n2. Delete\n3. Display\n4. Exit\nEnter your choice: ");
scanf("%d",&choice1);
switch()
{
case 1: printf("Enter the value to be inserted: ");
scanf("%d",&value);
while(1)
{
printf("\nSelect from the following Inserting options\n");
printf("1. At Beginning\n2. At End\n3. After a Node\n4. Cancel\nEnter your choice: ");
scanf("%d",&choice2);
switch(choice2)
{
case 1: insertAtBeginning(value);
break;
case 2: insertAtEnd(value);
break;
case 3: printf("Enter the location after which you want to insert: ");
scanf("%d",&location);
insertAfter(value,location);
break;
case 4: goto EndSwitch;
default: printf("\nPlease select correct Inserting option!!!\n");
}
}
case 2: while(1)
{
printf("\nSelect from the following Deleting options\n");
printf("1. At Beginning\n2. At End\n3. Specific Node\n4. Cancel\nEnter your choice: ");
scanf("%d",&choice2);
switch(choice2)
{
case 1: deleteBeginning();
break;
case 2: deleteEnd();
break;
case 3: printf("Enter the Node value to be deleted: ");
scanf("%d",&location);
deleteSpecic(location);
break;
case 4: goto EndSwitch;
default: printf("\nPlease select correct Deleting option!!!\n");
}
}
EndSwitch: break;
case 3: display();
break;
case 4: exit(0);
default: printf("\nPlease select correct option!!!");
}
}
}
In single linked list, every node points to its next node in the sequence and the last node points NULL. But in circular
linked list, every node points to its next node in the sequence but the last node points to the first node in the list.
A circular linked list is a sequence of elements in which every element has a link to its next element in the
sequence and the last element has a link to the first element.
That means circular linked list is similar to the single linked list except that the last node points to the first node in the
list
Example
Operations
In a circular linked list, we perform the following operations...
1. Insertion
2. Deletion
3. Display
Before we implement actual operations, first we need to setup empty list. First perform the following steps before
implementing actual operations.
Step 1 - Include all the header files which are used in the program.
Step 2 - Declare all the user defined functions.
Step 3 - Define a Node structure with two members data and next
Step 4 - Define a Node pointer 'head' and set it to NULL.
Step 5 - Implement the main method by displaying operations menu and make suitable function calls in the main
method to perform user selected operation.
Insertion
In a circular linked list, the insertion operation can be performed in three ways. They are as follows...
1. Inserting At Beginning of the list
2. Inserting At End of the list
3. Inserting At Specific location in the list
Inserting At Beginning of the list
We can use the following steps to insert a new node at beginning of the circular linked list...
Step 1 - Create a newNode with given value.
Step 2 - Check whether list is Empty (head == NULL)
Step 3 - If it is Empty then, set head = newNode and newNode→next = head .
Step 4 - If it is Not Empty then, define a Node pointer 'temp' and initialize with 'head'.
Step 5 - Keep moving the 'temp' to its next node until it reaches to the last node (until 'temp → next == head').
Step 6 - Set 'newNode → next =head', 'head = newNode' and 'temp → next = head'.
Inserting At End of the list
We can use the following steps to insert a new node at end of the circular linked list...
Step 1 - Create a newNode with given value.
Step 2 - Check whether list is Empty (head == NULL).
Step 3 - If it is Empty then, set head = newNode and newNode → next = head.
Step 4 - If it is Not Empty then, define a node pointer temp and initialize with head.
Step 5 - Keep moving the temp to its next node until it reaches to the last node in the list (until temp →
next == head).
Step 6 - Set temp → next = newNode and newNode → next = head.
Inserting At Specific location in the list (After a Node)
We can use the following steps to insert a new node after a node in the circular linked list...
Step 1 - Create a newNode with given value.
Step 2 - Check whether list is Empty (head == NULL)
Step 3 - If it is Empty then, set head = newNode and newNode → next = head.
Step 4 - If it is Not Empty then, define a node pointer temp and initialize with head.
Step 5 - Keep moving the temp to its next node until it reaches to the node after which we want to insert the
newNode (until temp1 → data is equal to location, here location is the node value after which we want to insert
the newNode).
Step 6 - Every time check whether temp is reached to the last node or not. If it is reached to last node then
display 'Given node is not found in the list!!! Insertion not possible!!!' and terminate the function. Otherwise
move the temp to next node.
Step 7 - If temp is reached to the exact node after which we want to insert the newNode then check whether it is
last node (temp → next == head).
Step 8 - If temp is last node then set temp → next = newNode and newNode → next = head.
Step 8 - If temp is not last node then set newNode → next = temp → next and temp → next = newNode.
Deletion
In a circular linked list, the deletion operation can be performed in three ways those are as follows...
1. Deleting from Beginning of the list
2. Deleting from End of the list
3. Deleting a Specific Node
Deleting from Beginning of the list
We can use the following steps to delete a node from beginning of the circular linked list...
Step 1 - Check whether list is Empty (head == NULL)
Step 2 - If it is Empty then, display 'List is Empty!!! Deletion is not possible' and terminate the function.
Step 3 - If it is Not Empty then, define two Node pointers 'temp1' and 'temp2' and initialize both 'temp1' and
'temp2' with head.
Step 4 - Check whether list is having only one node (temp1 → next == head)
Step 5 - If it is TRUE then set head = NULL and delete temp1 (Setting Empty list conditions)
Step 6 - If it is FALSE move the temp1 until it reaches to the last node. (until temp1 → next == head )
Step 7 - Then set head = temp2 → next, temp1 → next = head and delete temp2.
Deleting from End of the list
We can use the following steps to delete a node from end of the circular linked list...
Step 1 - Check whether list is Empty (head == NULL)
Step 2 - If it is Empty then, display 'List is Empty!!! Deletion is not possible' and terminate the function.
Step 3 - If it is Not Empty then, define two Node pointers 'temp1' and 'temp2' and initialize 'temp1' with head.
Step 4 - Check whether list has only one Node (temp1 → next == head)
Step 5 - If it is TRUE. Then, set head = NULL and delete temp1. And terminate from the function.
(Setting Empty list condition)
Step 6 - If it is FALSE. Then, set 'temp2 = temp1 ' and move temp1 to its next node. Repeat the same
until temp1 reaches to the last node in the list. (until temp1 → next == head)
Step 7 - Set temp2 → next = head and delete temp1.
Deleting a Specific Node from the list
We can use the following steps to delete a specific node from the circular linked list...
Step 1 - Check whether list is Empty (head == NULL)
Step 2 - If it is Empty then, display 'List is Empty!!! Deletion is not possible' and terminate the function.
Step 3 - If it is Not Empty then, define two Node pointers 'temp1' and 'temp2' and initialize 'temp1' with head.
Step 4 - Keep moving the temp1 until it reaches to the exact node to be deleted or to the last node. And every time
set 'temp2 = temp1' before moving the 'temp1' to its next node.
Step 5 - If it is reached to the last node then display 'Given node not found in the list! Deletion not possible!!!'.
And terminate the function.
Step 6 - If it is reached to the exact node which we want to delete, then check whether list is having only one node
(temp1 → next == head)
Step 7 - If list has only one node and that is the node to be deleted then set head = NULL and
delete temp1 (free(temp1)).
Step 8 - If list contains multiple nodes then check whether temp1 is the first node in the list (temp1 == head).
Step 9 - If temp1 is the first node then set temp2 = head and keep moving temp2 to its next node
until temp2 reaches to the last node. Then set head = head → next, temp2 → next = head and delete temp1.
Step 10 - If temp1 is not first node then check whether it is last node in the list (temp1 → next == head).
Step 1 1- If temp1 is last node then set temp2 → next = head and delete temp1 (free(temp1)).
Step 12 - If temp1 is not first node and not last node then set temp2 → next = temp1 → next and
delete temp1 (free(temp1)).
Displaying a circular Linked List
We can use the following steps to display the elements of a circular linked list...
Step 1 - Check whether list is Empty (head == NULL)
Step 2 - If it is Empty, then display 'List is Empty!!!' and terminate the function.
Step 3 - If it is Not Empty then, define a Node pointer 'temp' and initialize with head.
Step 4 - Keep displaying temp → data with an arrow (--->) until temp reaches to the last node
Step 5 - Finally display temp → data with arrow pointing to head → data.
Example
Following are the implementations of this operation in C
#include<stdio.h>
#include<conio.h>
void insertAtBeginning(int);
void insertAtEnd(int);
void insertAtAfter(int,int);
void deleteBeginning();
void deleteEnd();
void deleteSpecific(int);
void display();
struct Node
{
int data;
struct Node *next;
}*head = NULL;
void main()
{
int choice1, choice2, value, location;
clrscr();
while(1)
{
printf("\n*********** MENU *************\n");
printf("1. Insert\n2. Delete\n3. Display\n4. Exit\nEnter your choice: ");
scanf("%d",&choice1);
switch()
{
case 1: printf("Enter the value to be inserted: ");
scanf("%d",&value);
while(1)
{
printf("\nSelect from the following Inserting options\n");
printf("1. At Beginning\n2. At End\n3. After a Node\n4. Cancel\nEnter your choice: ");
scanf("%d",&choice2);
switch(choice2)
{
case 1: insertAtBeginning(value);
break;
case 2: insertAtEnd(value);
break;
case 3: printf("Enter the location after which you want to insert: ");
scanf("%d",&location);
insertAfter(value,location);
break;
case 4: goto EndSwitch;
default: printf("\nPlease select correct Inserting option!!!\n");
}
}
case 2: while(1)
{
printf("\nSelect from the following Deleting options\n");
printf("1. At Beginning\n2. At End\n3. Specific Node\n4. Cancel\nEnter your choice: ");
scanf("%d",&choice2);
switch(choice2)
{
case 1: deleteBeginning();
break;
case 2: deleteEnd();
break;
case 3: printf("Enter the Node value to be deleted: ");
scanf("%d",&location);
deleteSpecic(location);
break;
case 4: goto EndSwitch;
default: printf("\nPlease select correct Deleting option!!!\n");
}
}
EndSwitch: break;
case 3: display();
break;
case 4: exit(0);
default: printf("\nPlease select correct option!!!");
}
}
}
If we know the value of x then we can raise the power of x or the exponent of x and then multiply with the
coefficient. So, in this way, we can get the answer for one term. Likewise, we can evaluate all the terms and
get the result. It is sufficient to store the coefficient and exponent of each term. We can represent these terms
in the form of a linked list. So, we can define each term as a node and we will have a set of nodes for a single
polynomial. So let us define a node for a single term.
Here is the node that is having a coefficient, an exponent, and a pointer (next) to the next node. Let us define
the structure for this.
struct Node {
int coefficient;
int exponent;
}
This structure has a coefficient and exponent of type int and a pointer to the next node of type Node*. If the
coefficient is in decimal, then you can take float also. Now one more thing you can observe, this is the node
of the linked list and it is having 3 members. So, the linked list that we have studied was taking only one
value but now we are using a linked list. Based on the requirements a node can have any number of data
members. Now, let us represent the polynomial as a linked list.
Here we have a linked type Node which we have created above. Each node has 3 sections: coefficient,
exponent, and a pointer to the next node. There are 4 terms in the above polynomial so we have taken 4 nodes
here. Let us fill up the value from the above polynomial.
This is how a polynomial is represented in the form of a linked list. In array representation, we were storing
the number of terms but here we do not need to store. If you want then you can store that otherwise, the
number of nodes is equal to the total number of terms. Here, we will not have extra nodes. Now we will learn
how to evaluate the polynomial. We have stored all the required data to evaluate a polynomial value but one
more thing is required which is the value of ‘x’.
P(x) = 4x3 + 9x2 + 6x + 7
If suppose the value of ‘x’ is 1 then,
P (x) = (4 * 13) + (9 * 12) + (6 * 1) + 7
P (x) = 4 + 9 + 6 + 7
P (x) = 26
If the value of ‘x’ is 2 then,
P(x) = (4 * 23) + (9 * 22) + (6 * 2) + 7
So, with the value of x, we can evaluate the result of the polynomial. Let us look at the procedure of
evaluation. Suppose the value of ‘x’ is 2 then, we have to raise the power of x and then multiply it with the
coefficient. This will give the result of a single term. Then we have to add the result of the previous term
with the result of the next term. So, we have to add the result of all the terms to get the result. We will take a
variable i.e. sum which will store the addition of the result of all the previous terms. For example,
P (x) = 4x3 + 9x2 + 6x + 7
Sum = 0;
To evaluate 4x3,
4 * 23 = 32
Sum = 0 + 32 = 32
To evaluate 9x2,
9 * 22 = 36
Sum = 32 + 36 = 68
To evaluate 6x,
6 * 2 = 12
Sum = 68 + 12 = 80
To evaluate 7,
Sum = 80 + 7 = 87
So, when we put the value of ‘x’ as 2 then we will get 87 as the result of the polynomial. Now we will write
a function for evaluation.
Node *q = p;
while(q != NULL){
q = q->next;
return sum;
}
So, this evaluate function will take the value of ‘x’ as a parameter and it will traverse the nodes of the linked
list, and at each node, it will store the sum of all the previous nodes or terms and at last, it will return the
sum. Now let us look at the complete program.
C Program to understand the Evaluation of polynomials using Linked List Representation:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
struct Node {
int coeff;
int exp;
}* poly = NULL;
void create() {
int num, i;
if (poly == NULL) {
poly = last = t;
} else {
last = t;
p = p -> next;
while (p) {
p = p -> next;
printf("\n");
long val = 0;
while (p) {
p = p -> next;
return val;
int main() {
int x;
create();
Display(poly);
printf("Enter value of x: ");
scanf("%d", &x);
return 0;
Sparse Matrix and its representations (Using Arrays and Linked Lists)
A matrix is a two-dimensional data object made of m rows and n columns, therefore having total m x
n values. If most of the elements of the matrix have 0 value, then it is called a sparse matrix.
Why to use Sparse Matrix instead of simple matrix ?
• Storage: There are lesser non-zero elements than zeros and thus lesser memory can be used to
store only those elements.
• Computing time: Computing time can be saved by logically designing a data structure
traversing only non-zero elements..
Example:
00304
00570
00000
02600
Representing a sparse matrix by a 2D array leads to wastage of lots of memory as zeroes in the
matrix are of no use in most of the cases. So, instead of storing zeroes with non-zero elements, we
only store non-zero elements. This means storing non-zero elements with triples- (Row, Column,
value).
Sparse Matrix Representations can be done in many ways following are two common
representations:
1. Array representation
2. Linked list representation
int main()
{
// Assume 4x5 sparse matrix
int sparseMatrix[4][5] =
{
{0 , 0 , 3 , 0 , 4 },
{0 , 0 , 5 , 7 , 0 },
{0 , 0 , 0 , 0 , 0 },
{0 , 2 , 6 , 0 , 0 }
};
int size = 0;
for (int i = 0; i < 4; i++)
for (int j = 0; j < 5; j++)
if (sparseMatrix[i][j] != 0)
size++;
printf("\n");
}
return 0;
}
Output
001133
242312
345726
Time Complexity: O(NM), where N is the number of rows in the sparse matrix, and M is the
number of columns in the sparse matrix.
Auxiliary Space: O(NM), where N is the number of rows in the sparse matrix, and M is the number
of columns in the sparse matrix.
}
}
printf("row_position: ");
while(temp != NULL)
{
printf("column_postion: ");
while(r != NULL)
{
printf("%d ", r->column_postion);
r = r->next;
}
printf("\n");
printf("Value: ");
while(s != NULL)
{
printf("%d ", s->value);
s = s->next;
}
printf("\n");
}
PrintList(start);
return 0;
}
Output
row_position:0 0 1 1 3 3
column_position:2 4 2 3 1 2
Value:3 4 5 7 2 6
Time Complexity: O(N*M), where N is the number of rows in the sparse matrix, and M is the
number of columns in the sparse matrix.
Auxiliary Space: O(K), where K is the number of non-zero elements in the array.