0% found this document useful (0 votes)
13 views

ds unit1

aktu data structure third semester unit one notes

Uploaded by

Ak
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
13 views

ds unit1

aktu data structure third semester unit one notes

Uploaded by

Ak
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 99

Data Structure

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:

Algorithm + Data structure = Program

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.

Characteristics of a Data Structure


• Correctness − Data structure implementation should implement its interface correctly.
• Time Complexity − Running time or the execution time of operations of data structure must be
as small as possible.
• Space Complexity − Memory usage of a data structure operation should be as little as possible.

Need for Data Structure


As applications are getting complex and data rich, there are three common problems that applications
face now-a-days.
• Data Search − Consider an inventory of 1 million(106) items of a store. If the application is to
search an item, it has to search an item in 1 million(106) items every time slowing down the
search. As data grows, search will become slower.
• Processor speed − Processor speed although being very high, falls limited if the data grows to
billion records.
• Multiple requests − As thousands of users can search data simultaneously on a web server, even
the fast server fails while searching the data.
To solve the above-mentioned problems, data structures come to rescue. Data can be organized in a
data structure in such a way that all items may not be required to be searched, and the required data can
be searched almost instantly.

Advantages of Data Structures


• Efficiency: Efficiency of a program depends upon the choice of data structures. For example:
suppose, we have some data and we need to perform the search for a perticular record. In that
case, if we organize our data in an array, we will have to search sequentially element by element.
hence, using array may not be very efficient here. There are better data structures which can
make the search process efficient like ordered array, binary search tree or hash tables.
• Reusability: Data structures are reusable, i.e. once we have implemented a particular data
structure, we can use it at any other place. Implementation of data structures can be compiled
into libraries which can be used by different clients.
• Abstraction: Data structure is specified by the ADT which provides a level of abstraction. The
client program uses the data structure through interface only, without getting into the
implementation details.

Execution Time Cases


There are three cases which are usually used to compare various data structure's execution time in a
relative manner.
• Worst Case − This is the scenario where a particular data structure operation takes maximum
time it can take. If an operation's worst case time is ƒ(n) then this operation will not take more
than ƒ(n) time where ƒ(n) represents function of n.
• Average Case − This is the scenario depicting the average execution time of an operation of a
data structure. If an operation takes ƒ(n) time in execution, then m operations will take mƒ(n)
time.
• Best Case − This is the scenario depicting the least possible execution time of an operation of a
data structure. If an operation takes ƒ(n) time in execution, then the actual operation may take
time as the random number which would be maximum as ƒ(n).
Basic Terminology: Elementary Data Organization

Data: Data are simply values or sets of values.

Data items: Data items refers to a single unit of values

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

An entity is something that has certain attributes or properties which may be


assignedvalues. The values may be either numeric or non-numeric.

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.

Meaningful or processed data is called information. The collection of data is


organized into the hierarchy of fields, records and files

Field It is a single elementary unit of information representing an attribute of an entity.

Record is the collection of field values of a given entity.

is the collection of records of the entities in a given entity set.

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.

Records may also be classified according to length.

A file can have fixed-length records or variable-length records.


• In fixed-length records, all the records contain the same data items with
the same amountof space assigned to each data item.
• In variable-length records file records may contain different lengths.
Example: Student records have variable lengths, since different students take
different numbers of courses. Variable-length records have a minimum and a
maximum length.
The above organization of data into fields, records and files may not be complex
enough to maintain and efficiently process certain collections of data. For this
reason, data are also organized into more complex types of structures.

The study of complex data structures includes the following three steps:

1. Logical or mathematical description of the structure


2. Implementation of the structure on a computer
3. Quantitative analysis of the structure, which includes determining the amount
of memory needed to store the structure and the time required to process the
structure.

Data structures: Organization of data

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:

Contiguous structures can be broken drawn further into two kinds:


• those that contain data items of all the same size
• those where the size may differ.
The first kind is called the array. Figure 1.3(a. array) shows an example of an array of numbers. In an
array, each element is of the same type, and thus has the same size.
The second kind of contiguous structure is called structure, figure 1.3(b. struct) shows a simple structure
consisting of a person‘s name and age. In a struct, elements may be of different data types and thus may
have different sizes.

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.

Data structure example


Suppose we have an index array of size 4. We have an index location starting from 0, 1, 2, 3. Array is a
data structure where the elements are stored in a contiguous location. The memory address of the first
element is 1000, second element is 1004, third element is 1008, and the fourth element is 1012. Since it
is of integer type so it will occupy 4 bytes and the difference between the addresses of each element is 4
bytes. The values stored in an array are 10, 20, 30 and 40. These values, index positions and the memory
addresses are the implementations.
The abstract or logical view of the integer array can be stated as:
o It stores a set of elements of integer type.
o It reads the elements by position, i.e., index.
o It modifies the elements by index
o It performs sorting
The implementation view of the integer array:
a[4] = {10, 20, 30, 40}
cout<< a[2]
a[3] = 50
---------------------------------------------------------------------------------------------------------------
✓ The user does not need to know how that data type is implemented, for example, we have been
using Primitive values like int, float, char data types only with the knowledge that these data
type can operate and be performed on without any idea of how they are implemented.
✓ So a user only needs to know what a data type can do, but not how it will be implemented.
Think of ADT as a black box which hides the inner structure and design 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”.

Therefore Algorithm refers to a sequence of finite steps to solve a particular problem.

Use of the Algorithms:


Algorithms play a crucial role in various fields and have many applications. Some of the key
areas where algorithms are used include:

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.

What is the need for algorithms?


1. Algorithms are necessary for solving complex problems efficiently and effectively.
2. They help to automate processes and make them more reliable, faster, and easier to perform.
3. Algorithms also enable computers to perform tasks that would be difficult or impossible
for humans to do manually.
4. They are used in various fields such as mathematics, computer science, engineering,
finance, and many others to optimize processes, analyze data, make predictions, and
provide solutions to problems.

What are the 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 are an algorithm.

For some instructions to be an algorithm, it must have the following characteristics:


• Clear and Unambiguous: The algorithm should be unambiguous. Each of its steps should
be clear in all aspects and must lead to only one meaning.
• Well-Defined Inputs: If an algorithm says to take inputs, it should be well-defined inputs.
It may or may not take input.
• 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.
• Finite-ness: The algorithm must be finite, i.e. it should terminate after a finite time.
• 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.
• 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.
• Input: An algorithm has zero or more inputs. Each that contains a fundamental operator
must accept zero or more inputs.
• Output: An algorithm produces at least one output. Every instruction that contains a
fundamental operator must accept zero or more inputs.
• 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.
• Finiteness: An algorithm must terminate after a finite number of steps in all test cases.
Every instruction which contains a fundamental operator must be terminated within a finite
amount of time. Infinite loops or recursive functions without base conditions do not possess
finiteness.
• 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.
Properties of Algorithm:
• It should terminate after a finite time.
• It should produce at least one output.
• It should take zero or more input.
• It should be deterministic means giving the same output for the same input case.
• Every step in the algorithm must be effective i.e. every step should do some work.

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.

7. Divide and Conquer Algorithm:

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.

9. Dynamic Programming Algorithm:

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.

10. Randomized Algorithm:

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.

Step 1: Fulfilling the pre-requisites

As discussed above, to write an algorithm, its prerequisites must be fulfilled.


1. The problem that is to be solved by this algorithm: Add 3 numbers and print their sum.
2. The constraints of the problem that must be considered while solving the problem: The numbers must
contain only digits and no other characters.
3. The input to be taken to solve the problem: The three numbers to be added.
4. The output to be expected when the problem is solved: The sum of the three numbers taken as the input
i.e. a single integer value.
5. The solution to this problem, in the given constraints: The solution consists of adding the 3 numbers. It
can be done with the help of the ‘+’ operator, or bit-wise, or any other method.

Step 2: Designing the algorithm


Now let’s design the algorithm with the help of the above pre-requisites:
Algorithm to add 3 numbers and print their sum:
1. START
2. Declare 3 integer variables num1, num2, and num3.
3. Take the three numbers, to be added, as inputs in variables num1, num2, and num3 respectively.
4. Declare an integer variable sum to store the resultant sum of the 3 numbers.
5. Add the 3 numbers and store the result in the variable sum.
6. Print the value of the variable sum
7. END

Step 3: Testing the algorithm by implementing it.


• To test the algorithm, let’s implement it in C language.
Program:

// C program to add three numbers


// with the help of above designed algorithm

#include <stdio.h>
int main()
{

// Variables to take the input of the 3 numbers


int num1, num2, num3;

// Variable to store the resultant sum


int sum;

// Take the 3 numbers as input


printf("Enter the 1st number: ");
scanf("%d", &num1);
printf("%d\n", num1);

printf("Enter the 2nd number: ");


scanf("%d", &num2);
printf("%d\n", num2);

printf("Enter the 3rd number: ");


scanf("%d", &num3);
printf("%d\n", num3);

// Calculate the sum using + operator


// and store it in variable sum
sum = num1 + num2 + num3;

// Print the sum


printf("\nSum of the 3 numbers is: %d", sum);

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.

Why Analysis of Algorithms is important?


• To predict the behaviour of an algorithm without implementing it on a specific computer.
• It is much more convenient to have simple measures for the efficiency of an algorithm than to
implement the algorithm and test the efficiency every time a certain parameter in the underlying
computer system changes.
• It is impossible to predict the exact behaviour of an algorithm. There are too many influencing
factors.
• The analysis is thus only an approximation; it is not perfect.
• More importantly, by analysing different algorithms, we can compare them to determine the best
one for our purpose.

The performance of the algorithm can be measured in two factors:


Time complexity:
o The time complexity of an algorithm is the amount of time required to complete the execution.
o The time complexity of an algorithm is denoted by the big O notation. Here, big O notation is
the asymptotic notation to represent the time complexity.
o The time complexity is mainly calculated by counting the number of steps to finish the execution.
o Let's understand the time complexity through an example.
sum=0;
// Suppose we have to calculate the sum of n numbers.
for i=1 to n
sum=sum+i;
// when the loop ends then sum holds the sum of the n numbers
return sum;
o In the above code, the time complexity of the loop statement will be atleast n, and if the value of
n increases, then the time complexity also increases.
o While the complexity of the code, i.e., return sum will be constant as its value is not dependent
on the value of n and will provide the result in one step only. We generally consider the worst-
time complexity as it is the maximum time taken for any given input size.
Space complexity:
o An algorithm's space complexity is the amount of space required to solve a problem and produce
an output. Similar to the time complexity, space complexity is also expressed in big O notation.
o For an algorithm, the space is required for the following purposes:
1. To store program instructions
2. To store constant values
3. To store variable values
4. To track the function calls, jumping statements, etc.
o Auxiliary space: The extra space required by the algorithm, excluding the input size, is known
as an auxiliary space. The space complexity considers both the spaces, i.e., auxiliary space, and
space used by the input.
So,
Space complexity = Auxiliary space + Input size.

Types of Algorithm Analysis:

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

% of running time due to 5n2 = * 100 = 21.74%

% of running time due to 6n = * 100 = 26.09%

% of running time due to 12 = * 100 = 52.17%


From the above calculation, it is observed that most of the time is taken by 12. But, we have to find the
growth rate of f(n), we cannot say that the maximum amount of time is taken by 12. Let's assume the
different values of n to find the growth rate of f(n).
n 5n2 6n 12

1 21.74% 26.09% 52.17%

10 87.41% 10.49% 2.09%

100 98.79% 1.19% 0.02%

1000 99.88% 0.12% 0.0002%

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 (Here complexity can be Space Complexity or Time Complexity).

For example, consider the following time complexities of two algorithms...

• 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

time complexity of an algorithm.

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.

Big - Oh Notation can be defined as follows...

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

Consider the following f(n) and g(n)...

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

f(n) <= C g(n)

⇒3n + 2 <= C n

Above condition is always TRUE for all values of C = 4 and n >= 2.

By using Big - Oh notation we can represent the time complexity as follows...

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.

Omega Notation can be defined as follows...

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

Consider the following f(n) and g(n)...

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

f(n) >= C g(n)

⇒3n + 2 >= C n

Above condition is always TRUE for all values of C = 1 and n >= 1.

By using Omega notation we can represent the time complexity as follows...

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.

Theta Notation can be defined as follows...

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

indicates the algorithm's average bound.

Example

Consider the following f(n) and g(n)...

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

C1 g(n) <= f(n) <= C2 g(n)

⇒C1 n <= 3n + 2 <= C2 n

Above condition is always TRUE for all values of C1 = 1, C2 = 4 and n >= 2.


By using Theta notation we can represent the time compexity as follows...

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.

An array can also be defined as follows...

"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

Calculate the address of any element in the 1-D array:


A 1-dimensional array (or single-dimension array) is a type of linear array. Accessing its
elements involves a single subscript that can either represent a row or column index.
Example:
1-D array

To find the address of an element in an array the following formula is used-


Address of A[I] = B + W * (I – LB)
I = Subset of element whose address to be found,
B = Base address,
W = Storage size of one element store in any array(in byte),
LB = Lower Limit/Lower Bound of subscript(If not specified assume zero).

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

Calculate the address of any element in the 2-D array:


The 2-dimensional array can be defined as an array of arrays. The 2-Dimensional arrays are organized
as matrices which can be represented as the collection of rows and columns as array[M][N] where M is
the number of rows and N is the number of columns.
Example:
To find the address of any element in a 2-Dimensional array there are the following two ways-
1. Row Major Order
2. Column Major Order
1. Row Major Order:
Row major ordering assigns successive elements, moving across the rows and then down the next row,
to successive memory locations. In simple language, the elements of an array are stored in a Row-Wise
fashion.
To find the address of the element using row-major order uses the following formula:
Address of A[I][J] = B + W * ((I – LR) * N + (J – LC))
I = Row Subset of an element whose address to be found,
J = Column Subset of an element whose address to be found,
B = Base address,
W = Storage size of one element store in an array(in byte),
LR = Lower Limit of row/start row index of the matrix(If not given assume it as zero),
LC = Lower Limit of column/start column index of the matrix(If not given assume it as zero),
N = Number of column given in the matrix.

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

A[6,2] = 100 + ((2*5 + 3))2 = 100+(13 * 2) =100 + 26 =126

2. Column Major Order:


If elements of an array are stored in a column-major fashion means moving across the column and then
to the next column then it’s in column-major order. To find the address of the element using column-
major order use the following formula:
Address of A[I][J] = B + W * ((J – LC) * M + (I – LR))
I = Row Subset of an element whose address to be found,
J = Column Subset of an element whose address to be found,
B = Base address,
W = Storage size of one element store in any array(in byte),
LR = Lower Limit of row/start row index of matrix(If not given assume it as zero),
LC = Lower Limit of column/start column index of matrix(If not given assume it as zero),
M = Number of rows given in the matrix.
Example: Given an array arr[1………10][1………15] with a base value of 100 and the size of each
element is 1 Byte in memory find the address of arr[8][6] with the help of column-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 Rows given in the matrix M = Upper Bound – Lower Bound + 1
= 10 – 1 + 1
= 10
Formula: used
Address of A[I][J] = B + W * ((J – LC) * M + (I – LR))
Address of A[8][6] = 100 + 1 * ((6 – 1) * 10 + (8 – 1))
= 100 + 1 * ((5) * 10 + (7))
= 100 + 1 * (57)
Address of A[I][J] = 157
From the above examples, it can be observed that for the same position two different address locations
are obtained that’s because in row-major order movement is done across the rows and then down to the
next row, and in column-major order, first move down to the first column and then next column. So
both the answers are right.
So it’s all based on the position of the element whose address is to be found for some cases the same
answers is also obtained with row-major order and column-major order and for some cases, different
answers are obtained.

Calculating the address of an element in an N-dimensional array

N-Dimensional Arrays: The N-Dimensional array is basically an array of arrays.


• 1-D arrays are identified as a single index, Syntax: int A[S1]
• 2-D arrays are identified using two indices, Syntax: int A[S1][S2]
• N-Dimensional arrays are identified using N indices.
• A multi-dimensional array is declared as follows:
int A[S1][S2][S3]……..[SN];
Explanation:
• Here, A is the name of the N-Dimensional array. It can be any valid identifier name.
• In the above syntax, S1, S2, S3……SN denotes the max sizes of the N dimensions.
• The lower bounds are assumed to be zeroes for all the dimensions.
• The above array is declared as an integer array. It can be any valid data type other than an integer as
well.

Address Calculation of N-Dimensional Arrays:

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

Here, BA = 1200 and width = 4.


S1 = 10, S2 = 20, S3 = 30, S4 = 40
Since the lower bounds are not given, so lower bounds are assumed to be zero.
E1 = 1 – 0 = 1;
E2 = 3 – 0 = 3;
E3 = 5 – 0 = 5;
E4 = 6 – 0 = 6.
Any of the techniques (Row-major or column-major) can be used for calculating the answer (unless
specified).

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

Block Subset of an element whose address to be found I = 10


= I - lower bound of row [L]
= 10 - 2
=8

Row Subset of an element whose address to be found J = 20


= j - lower bound of columns [M]
= 20 - 2
= 18

Column Subset of an element whose address to be found K = 30


= k - lower bound of blocks [N]
= 30 - 2
= 28

Base address B = 1000


Storage size of one element store in any array(in Byte) W = 2

Address of[i][j][k] = B + W(M * N *I) + ( J*N) + K)


= 1000 + 2 [(31*41*8) +(18*41) + 28]
=1000 + 2 [10168 + 738+ 28]
=1000 + 2 [10934]
=1000 + 21868
= 22868

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

Row Subset of an element whose address to be found I = 10


= I - lower bound of row[L]
= 10 - 2
=8

Column Subset of an element whose address to be found J = 20


= J - lower bound of columns[M]
= 20 - 2
= 18

Block Subset of an element whose address to be found K = 30


= K - lower bound of blocks[N]
= 30 - 2
= 28
Base address B = 1000
Storage size of one element store in any array(in Byte) W = 2

Address of[i][j][k] = B + W(L * M *K) + ( J*L) + I)


= 1000 + 2 [(91*31*28) + (18*91) + 8]
=1000 + 2 [78988 + 1638 + 8]
=1000 + 2 [78988 + 1638 + 8]
=1000 + 2[80634]
= 162268
Solution 1:
Given, P [-2:2,2:22]
Q [1:8,-5:5,-10:5]
Stored in column major order in memory.
i. Length of each dimension of P:
Lpi=UBi-LBi+1
Lp1=UB1-LB1+1=2-(-2)+1=5
Lp2=UB2-LB2+1=22-2+1=21

Length of each dimension of Q:


Lqi=UBi-LBi+1
Lq1=UB1-LB1+1=8-1+1=8
Lq2=UB2-LB2+1=5-(-5)+1=11
Lq3=UB3-LB3+1=5-(-10)+1=16

ii. No. of elements in P= Lp1 *Lp2 =5*21=105


No. of elements in Q= Lq1 *Lq2 *Lq3 = 8*11*16=1408
iii. Q[1:8,-5:5,-10:5]
B=400,
W=4
LOC(Q[3,3,3])=?
Sol 1: Ei=Ki-LBi
E1= K1-LB1 = 3-1 = 2
E2= K2-LB2 = 3-(-5)= 8
E3= K3-LB3 = 3-(-10) = 13

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

Row Subset of an element whose address to be found I = 3


= I-lower bound of row[L]
=3-1
=2

Column Subset of an element whose address to be found J = 3


= I-lower bound of columns[M]
= (3-(-5))
=8

Block Subset of an element whose address to be found K = 3


= I-lower bound of blocks[N]
= 3-(-10)
= 13

Base address B = 400


Storage size of one element store in any array(in Byte) W = 4

Address of[i][j][k] = B + W(L * M(K – LB) + L * (J-LB) + (I – LB))


= 400 + 4[(8*11(3-(-10)) + 8*(3-(-5)) + (3-1)]
= 400 + 4[(88*13) + 64 + 2]
= 400 + 4 [1144 + 66]
= 400 + 4 [1210]
= 400 + 4840
= 5240

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.

Array contains following limitations:


1. The size of array must be known in advance before using it in the program.
2. Increasing size of the array is a time taking process. It is almost impossible to expand the size of
the array at run time.
3. All the elements in the array need to be contiguously stored in the memory. Inserting any element
in the array needs shifting of all its predecessors.

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.

Linked List Representation


Linked list can be visualized as a chain of nodes, where every node points to the next node.
As per the above illustration, following are the important points to be considered.
• Linked List contains a link element called first (head).
• Each link carries a data field(s) and a link field called next.
• Each link is linked with its next link using its next link.
• 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 −
B −> next = C;
ie B −> next = A −> next

It should look like this −

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 −

Insertion in linked list can be done in three different ways.


Insertion at Beginning
In this operation, we are adding an element at the beginning of the list.

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.

1. The node is being added to an empty list

2. The node is being added to the end of the linked list

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

Insertion at any position


In this operation, we are adding an element at any position within the list.
Algorithm
1. START
2. Create a new node and assign data to it
3. Iterate until the node at position is found
4. Point first to new first node
5. END
Insertion in singly linked list after specified Node

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

WRITE "EMPTY LIST"


GOTO STEP 7
END OF IF

3. REPEAT STEP 5 AND 6 UNTIL PTR != NULL

4. PRINT PTR→ DATA

5. PTR = PTR → NEXT

[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 "EMPTY LIST"


GOTO STEP 8
END OF IF

4. REPEAT STEP 5 TO 7 UNTIL PTR != NULL

5. if ptr → data = item

write I+1
End of IF

6. I = I + 1

7. PTR = PTR → NEXT

[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]

Step 2: SET PTR = HEAD

Step 3: SET HEAD = HEAD -> NEXT

Step 4: FREE PTR


Step 5: EXIT

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]

Step 2: SET PTR = HEAD

Step 3: Repeat Steps 4 and 5 while PTR -> NEXT!= NULL

Step 4: SET PREPTR = PTR

Step 5: SET PTR = PTR -> NEXT

[END OF LOOP]

Step 6: SET PREPTR -> NEXT = NULL

Step 7: FREE PTR

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

2. Iterate until find the current node at position in the list

3. Assign the adjacent node of current node in the list to its previous node.

4. END

Deletion in singly linked list after the specified node

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");
}
}
}

void insertAtBeginning(int value)


{
struct Node *newNode;
newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = value;
if(head == NULL)
{
newNode->next = NULL;
head = newNode;
}
else
{
newNode->next = head;
head = newNode;
}
printf("\nOne node inserted!!!\n");
}
void insertAtEnd(int value)
{
struct Node *newNode;
newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = value;
newNode->next = NULL;
if(head == NULL)
head = newNode;
else
{
struct Node *temp = head;
while(temp->next != NULL)
temp = temp->next;
temp->next = newNode;
}
printf("\nOne node inserted!!!\n");
}
void insertBetween(int value, int loc1, int loc2)
{
struct Node *newNode;
newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = value;
if(head == NULL)
{
newNode->next = NULL;
head = newNode;
}
else
{
struct Node *temp = head;
while(temp->data != loc1 && temp->data != loc2)
temp = temp->next;
newNode->next = temp->next;
temp->next = newNode;
}
printf("\nOne node inserted!!!\n");
}
void removeBeginning()
{
if(head == NULL)
printf("\n\nList is Empty!!!");
else
{
struct Node *temp = head;
if(head->next == NULL)
{
head = NULL;
free(temp);
}
else
{
head = temp->next;
free(temp);
printf("\nOne node deleted!!!\n\n");
}
}
}
void removeEnd()
{
if(head == NULL)
{
printf("\nList is Empty!!!\n");
}
else
{
struct Node *temp1 = head,*temp2;
if(head->next == NULL)
head = NULL;
else
{
while(temp1->next != NULL)
{
temp2 = temp1;
temp1 = temp1->next;
}
temp2->next = NULL;
}
free(temp1);
printf("\nOne node deleted!!!\n\n");
}
}
void removeSpecific(int delValue)
{
struct Node *temp1 = head, *temp2;
while(temp1->data != delValue)
{
if(temp1 -> next == NULL){
printf("\nGiven node not found in the list!!!");
goto functionEnd;
}
temp2 = temp1;
temp1 = temp1 -> next;
}
temp2 -> next = temp1 -> next;
free(temp1);
printf("\nOne node deleted!!!\n\n");
functionEnd:
}
void display()
{
if(head == NULL)
{
printf("\nList is Empty\n");
}
else
{
struct Node *temp = head;
printf("\n\nList elements are - \n");
while(temp->next != NULL)
{
printf("%d --->",temp->data);
temp = temp->next;
}
printf("%d --->NULL",temp->data);
}
}

Doubly Linked List

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

• In C, structure of a node in doubly linked list can be given as :


struct node
{
struct node *prev;
int data;
struct node *next;
}
✓ The prev part of the first node and the next part of the last node will always contain null
indicating end in each direction.
✓ In a singly linked list, we could traverse only in one direction, because each node contains
address of the next node and it doesn't have any record of its previous nodes. However,
doubly linked list overcome this limitation of singly linked list. Due to the fact that, each
node of the list contains the address of its previous node, we can find all the details about
the previous node as well by using the previous address stored inside the previous part of
each node.

Doubly Linked List Representation


• Each node carries a data field(s) and a link field called next and prev.
• Each link is linked with its next link using its next link.
• Each link is linked with its previous link using its prev link.
• The First node prev link carries a link as null to mark the First node of the list.
• The last node next link carries a link as null to mark the end of the list.

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.

Insertion at the Beginning


• In this operation, we create a new node with three compartments, one containing the data, the others
containing the address of its previous and next nodes in the list. This new node is inserted at the
beginning of the list.
Algorithm
1. START

2. Create a new node with three variables: prev, data, next.

3. Store the new data in the data variable

4. If the list is empty, make the new node as head.

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.

6. Point the head to the new node.


7. END

Algorithm :
Step 1: IF ptr = NULL

Write OVERFLOW
Go to Step 9
[END OF IF]

Step 2: SET NEW_NODE = ptr

Step 3: SET ptr = ptr -> NEXT

Step 4: SET NEW_NODE -> DATA = VAL

Step 5: SET NEW_NODE -> PREV = NULL

Step 6: SET NEW_NODE -> NEXT = START

Step 7: SET head -> PREV = NEW_NODE

Step 8: SET head = NEW_NODE

Step 9: EXIT

Insertion at the End


• In this insertion operation, the new input node is added at the end of the doubly linked list; if the list
is not empty. The head will be pointed to the new node, if the list is empty.
Algorithm
1. START

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]

Step 2: SET NEW_NODE = PTR

Step 3: SET PTR = PTR -> NEXT

Step 4: SET NEW_NODE -> DATA = VAL

Step 5: SET NEW_NODE -> NEXT = NULL

Step 6: SET TEMP = START

Step 7: Repeat Step 8 while TEMP -> NEXT != NULL

Step 8: SET TEMP = TEMP -> NEXT

[END OF LOOP]

Step 9: SET TEMP -> NEXT = NEW_NODE

Step 10C: SET NEW_NODE -> PREV = TEMP

Step 11: EXIT

Insertion in doubly linked list after Specified node


Algorithm
Step 1 - Create a newNode with given value.
Step 2 - Check whether list is Empty (head == NULL)
Step 3 - If it is Empty then, assign NULL to both newNode → previous & newNode → next and
set newNode to head.
Step 4 - If it is not Empty then, define two node pointers temp1 & temp2 and initialize temp1 with head.
Step 5 - Keep moving the temp1 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 temp1 is reached to the last node. If it is reached to the last node then
display 'Given node is not found in the list!!! Insertion not possible!!!' and terminate the function. Otherwise
move the temp1 to next node.
Step 7 - Assign temp1 → next to temp2, newNode to temp1 → next, temp1 to newNode →
previous, temp2 to newNode → next and newNode to temp2 → previous.

Traversing in doubly linked list


Algorithm
Step 1: IF HEAD == NULL

WRITE "UNDERFLOW"
GOTO STEP 6
[END OF IF]

Step 2: Set PTR = HEAD

Step 3: Repeat step 4 and 5 while PTR != NULL

Step 4: Write PTR → data

Step 5: PTR = PTR → next

Step 6: Exit

Searching for a specific node in Doubly Linked List


Algorithm
Step 1: IF HEAD == NULL

WRITE "UNDERFLOW"
GOTO STEP 8
[END OF IF]

Step 2: Set PTR = HEAD

Step 3: Set i = 0
Step 4: Repeat step 5 to 7 while PTR != NULL

Step 5: IF PTR → data = item

return i
[END OF IF]

Step 6: i = i + 1

Step 7: PTR = PTR → next

Step 8: Exit

Deletion at the Beginning


• This deletion operation deletes the existing first nodes in the doubly linked list. The head is shifted
to the next node and the link is removed.
Algorithm
1. START

2. Check the status of the doubly linked list

3. If the list is empty, deletion is not possible

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 2: SET PTR = HEAD

STEP 3: SET HEAD = HEAD → NEXT

STEP 4: SET HEAD → PREV = NULL


STEP 5: FREE PTR

STEP 6: EXIT

Deletion in doubly linked list at the end

Algorithm
Step 1: IF HEAD = NULL

Write UNDERFLOW
Go to Step 7
[END OF IF]

Step 2: SET TEMP = HEAD

Step 3: REPEAT STEP 4 WHILE TEMP->NEXT != NULL

Step 4: SET TEMP = TEMP->NEXT

[END OF LOOP]

Step 5: SET TEMP ->PREV-> NEXT = NULL

Step 6: FREE TEMP

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!!!");
}
}
}

void insertAtBeginning(int value)


{
struct Node *newNode;
newNode = (struct Node*)malloc(sizeof(struct Node));
newNode -> data = value;
newNode -> previous = NULL;
if(head == NULL)
{
newNode -> next = NULL;
head = newNode;
}
else
{
newNode -> next = head;
head = newNode;
}
printf("\nInsertion success!!!");
}
void insertAtEnd(int value)
{
struct Node *newNode;
newNode = (struct Node*)malloc(sizeof(struct Node));
newNode -> data = value;
newNode -> next = NULL;
if(head == NULL)
{
newNode -> previous = NULL;
head = newNode;
}
else
{
struct Node *temp = head;
while(temp -> next != NULL)
temp = temp -> next;
temp -> next = newNode;
newNode -> previous = temp;
}
printf("\nInsertion success!!!");
}
void insertAfter(int value, int location)
{
struct Node *newNode;
newNode = (struct Node*)malloc(sizeof(struct Node));
newNode -> data = value;
if(head == NULL)
{
newNode -> previous = newNode -> next = NULL;
head = newNode;
}
else
{
struct Node *temp1 = head, temp2;
while(temp1 -> data != location)
{
if(temp1 -> next == NULL)
{
printf("Given node is not found in the list!!!");
goto EndFunction;
}
else
{
temp1 = temp1 -> next;
}
}
temp2 = temp1 -> next;
temp1 -> next = newNode;
newNode -> previous = temp1;
newNode -> next = temp2;
temp2 -> previous = newNode;
printf("\nInsertion success!!!");
}
EndFunction:
}
void deleteBeginning()
{
if(head == NULL)
printf("List is Empty!!! Deletion not possible!!!");
else
{
struct Node *temp = head;
if(temp -> previous == temp -> next)
{
head = NULL;
free(temp);
}
else{
head = temp -> next;
head -> previous = NULL;
free(temp);
}
printf("\nDeletion success!!!");
}
}
void deleteEnd()
{
if(head == NULL)
printf("List is Empty!!! Deletion not possible!!!");
else
{
struct Node *temp = head;
if(temp -> previous == temp -> next)
{
head = NULL;
free(temp);
}
else{
while(temp -> next != NULL)
temp = temp -> next;
temp -> previous -> next = NULL;
free(temp);
}
printf("\nDeletion success!!!");
}
}
void deleteSpecific(int delValue)
{
if(head == NULL)
printf("List is Empty!!! Deletion not possible!!!");
else
{
struct Node *temp = head;
while(temp -> data != delValue)
{
if(temp -> next == NULL)
{
printf("\nGiven node is not found in the list!!!");
goto FuctionEnd;
}
else
{
temp = temp -> next;
}
}
if(temp == head)
{
head = NULL;
free(temp);
}
else
{
temp -> previous -> next = temp -> next;
free(temp);
}
printf("\nDeletion success!!!");
}
FuctionEnd:
}
void display()
{
if(head == NULL)
printf("\nList is Empty!!!");
else
{
struct Node *temp = head;
printf("\nList elements are: \n");
printf("NULL <--- ");
while(temp -> next != NULL)
{
printf("%d <===> ",temp -> data);
}
printf("%d ---> NULL", temp -> data);
}
}

Circular Linked List


What is Circular Linked List?

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!!!");
}
}
}

void insertAtBeginning(int value)


{
struct Node *newNode;
newNode = (struct Node*)malloc(sizeof(struct Node));
newNode -> data = value;
if(head == NULL)
{
head = newNode;
newNode -> next = head;
}
else
{
struct Node *temp = head;
while(temp -> next != head)
temp = temp -> next;
newNode -> next = head;
head = newNode;
temp -> next = head;
}
printf("\nInsertion success!!!");
}
void insertAtEnd(int value)
{
struct Node *newNode;
newNode = (struct Node*)malloc(sizeof(struct Node));
newNode -> data = value;
if(head == NULL)
{
head = newNode;
newNode -> next = head;
}
else
{
struct Node *temp = head;
while(temp -> next != head)
temp = temp -> next;
temp -> next = newNode;
newNode -> next = head;
}
printf("\nInsertion success!!!");
}
void insertAfter(int value, int location)
{
struct Node *newNode;
newNode = (struct Node*)malloc(sizeof(struct Node));
newNode -> data = value;
if(head == NULL)
{
head = newNode;
newNode -> next = head;
}
else
{
struct Node *temp = head;
while(temp -> data != location)
{
if(temp -> next == head)
{
printf("Given node is not found in the list!!!");
goto EndFunction;
}
else
{
temp = temp -> next;
}
}
newNode -> next = temp -> next;
temp -> next = newNode;
printf("\nInsertion success!!!");
}
EndFunction:
}
void deleteBeginning()
{
if(head == NULL)
printf("List is Empty!!! Deletion not possible!!!");
else
{
struct Node *temp = head;
if(temp -> next == head)
{
head = NULL;
free(temp);
}
else{
head = head -> next;
free(temp);
}
printf("\nDeletion success!!!");
}
}
void deleteEnd()
{
if(head == NULL)
printf("List is Empty!!! Deletion not possible!!!");
else
{
struct Node *temp1 = head, temp2;
if(temp1 -> next == head)
{
head = NULL;
free(temp1);
}
else{
while(temp1 -> next != head){
temp2 = temp1;
temp1 = temp1 -> next;
}
temp2 -> next = head;
free(temp1);
}
printf("\nDeletion success!!!");
}
}
void deleteSpecific(int delValue)
{
if(head == NULL)
printf("List is Empty!!! Deletion not possible!!!");
else
{
struct Node *temp1 = head, temp2;
while(temp1 -> data != delValue)
{
if(temp1 -> next == head)
{
printf("\nGiven node is not found in the list!!!");
goto FuctionEnd;
}
else
{
temp2 = temp1;
temp1 = temp1 -> next;
}
}
if(temp1 -> next == head){
head = NULL;
free(temp1);
}
else{
if(temp1 == head)
{
temp2 = head;
while(temp2 -> next != head)
temp2 = temp2 -> next;
head = head -> next;
temp2 -> next = head;
free(temp1);
}
else
{
if(temp1 -> next == head)
{
temp2 -> next = head;
}
else
{
temp2 -> next = temp1 -> next;
}
free(temp1);
}
}
printf("\nDeletion success!!!");
}
FuctionEnd:
}
void display()
{
if(head == NULL)
printf("\nList is Empty!!!");
else
{
struct Node *temp = head;
printf("\nList elements are: \n");
while(temp -> next != head)
{
printf("%d ---> ",temp -> data);
}
printf("%d ---> %d", temp -> data, head -> data);
}
}

Polynomial Representation using Linked List


P(x) = 4x3 + 9x2 + 6x + 7
This is a univariate polynomial. It is defined in terms of just a single variable ‘x’. It is the summation of
terms. There is more than one term being added together. If we know the value of ‘x’ we can evaluate and
get a result of this polynomial.
We want to represent this in our applications. So, for representation, we have to store the data about that
polynomial. That data can be stored either in an array or a linked list. So, we have already seen array
representation. Now we will see how to represent the data related to polynomials. If we observe the above
polynomial, each term is having its coefficient and exponent.

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;

struct Node *next;

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

double evaluate (int x){

double sum = 0.0;

Node *q = p;

while(q != NULL){

sum += q->coff * pow(x, q->exp);

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;

struct Node * next;

}* poly = NULL;

void create() {

struct Node * t, * last = NULL;

int num, i;

printf("Enter number of terms: ");

scanf("%d", & num);

printf("Enter each term with coeff and exp:\n");

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

t = (struct Node * ) malloc(sizeof(struct Node));

scanf("%d%d", & t -> coeff, & t -> exp);


t -> next = NULL;

if (poly == NULL) {

poly = last = t;

} else {

last -> next = t;

last = t;

void Display(struct Node * p) {

printf("%dx%d ", p -> coeff, p -> exp);

p = p -> next;

while (p) {

printf("+ %dx%d ", p -> coeff, p -> exp);

p = p -> next;

printf("\n");

long Eval(struct Node * p, int x) {

long val = 0;

while (p) {

val += p -> coeff * pow(x, p -> exp);

p = p -> next;

return val;

int main() {

int x;

create();

Display(poly);
printf("Enter value of x: ");

scanf("%d", &x);

printf("%ld\n", Eval(poly, 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

Method 1: Using Arrays:


2D array is used to represent a sparse matrix in which there are three rows named as
• Row: Index of row, where non-zero element is located
• Column: Index of column, where non-zero element is located
• Value: Value of the non zero element located at index – (row,column)
/ C++ program for Sparse Matrix Representation
// using Array
#include<stdio.h>

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++;

// number of columns in compactMatrix (size) must be


// equal to number of non - zero elements in
// sparseMatrix
int compactMatrix[3][size];

// Making of new matrix


int k = 0;
for (int i = 0; i < 4; i++)
for (int j = 0; j < 5; j++)
if (sparseMatrix[i][j] != 0)
{
compactMatrix[0][k] = i;
compactMatrix[1][k] = j;
compactMatrix[2][k] = sparseMatrix[i][j];
k++;
}

for (int i=0; i<3; i++)


{
for (int j=0; j<size; j++)
printf("%d ", compactMatrix[i][j]);

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.

Method 2: Using Linked Lists


In linked list, each node has four fields. These four fields are defined as:
• Row: Index of row, where non-zero element is located
• Column: Index of column, where non-zero element is located
• Value: Value of the non zero element located at index – (row,column)
• Next node: Address of the next node

// C program for Sparse Matrix Representation


// using Linked Lists
#include<stdio.h>
#include<stdlib.h>

// Node to represent sparse matrix


struct Node
{
int value;
int row_position;
int column_postion;
struct Node *next;
};

// Function to create new node


void create_new_node(struct Node** start, int non_zero_element,
int row_index, int column_index )
{
struct Node *temp, *r;
temp = *start;
if (temp == NULL)
{
// Create new node dynamically
temp = (struct Node *) malloc (sizeof(struct Node));
temp->value = non_zero_element;
temp->row_position = row_index;
temp->column_postion = column_index;
temp->next = NULL;
*start = temp;
}
else
{
while (temp->next != NULL)
temp = temp->next;

// Create new node dynamically


r = (struct Node *) malloc (sizeof(struct Node));
r->value = non_zero_element;
r->row_position = row_index;
r->column_postion = column_index;
r->next = NULL;
temp->next = r;

}
}

// This function prints contents of linked list


// starting from start
void PrintList(struct Node* start)
{
struct Node *temp, *r, *s;
temp = r = s = start;

printf("row_position: ");
while(temp != NULL)
{

printf("%d ", temp->row_position);


temp = temp->next;
}
printf("\n");

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");
}

// Driver of the program


int main()
{
// Assume 4x5 sparse matrix
int sparseMatric[4][5] =
{
{0 , 0 , 3 , 0 , 4 },
{0 , 0 , 5 , 7 , 0 },
{0 , 0 , 0 , 0 , 0 },
{0 , 2 , 6 , 0 , 0 }
};

/* Start with the empty list */


struct Node* start = NULL;

for (int i = 0; i < 4; i++)


for (int j = 0; j < 5; j++)

// Pass only those values which are non - zero


if (sparseMatric[i][j] != 0)
create_new_node(&start, sparseMatric[i][j], i, j);

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.

You might also like