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

DS Unit 1 Notes 24-25

Uploaded by

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

DS Unit 1 Notes 24-25

Uploaded by

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

Data Structure- Unit I

Introduction: Data Structure is a way to store and organized data to make it


efficient for user and programmer
Or
Data Structure is a technique or method of study, how the data are interacted to each
other logically and mathematically.
Or
A data structure is a particular way of organizing data in a computer so that it can be
used effectively. The idea is to reduce the space and time complexities of different
tasks.

Need of Data Structure:


The structure of the data and the synthesis of the algorithm are relative to each
other. Data presentation must be easy to understand so the developer, as well as
the user, can make an efficient implementation of the operation.
Data structures provide an easy way of organizing, retrieving, managing, and
storing data.
Here is a list of the needs for data.
 Data structure modification is easy.
 It requires less time.
 Save storage memory space.
 Data Representation is easy.
 Easy access to the large database

Some of the Significant Features of Data Structures are:


Robustness: Generally, all computer programmers aim to produce software that
yields correct output for every possible input, along with efficient execution on all
hardware platforms. This type of robust software must manage both valid and
invalid inputs.
Adaptability: Building software applications like Web Browsers, Word Processors,
and Internet Search Engine include huge software systems that require correct and
efficient working or execution for many years. Moreover, software evolves due to
emerging technologies or ever-changing market conditions.
Reusability: The features like Reusability and Adaptability go hand in hand. It is
known that the programmer needs many resources to build any software, making it
a costly enterprise. However, if the software is developed in a reusable and
adaptable way, then it can be applied in most future applications. Thus, by
executing quality data structures, it is possible to build reusable software, which
appears to be cost-effective and timesaving.

Classification of Data Structure:


Data structure has many different uses in our daily life. There are many different
data structures that are used to solve different mathematical and logical problems.
By using data structure, one can organize and process a very l arge amount of data
in a relatively short period. Let’s look at different data structures that are used in
different situations.

Primitive Data Structures


 Primitive Data Structures are the data structures consisting of the numbers
and the characters that come in-built into programs.
 These data structures can be manipulated or operated directly by machine -level
instructions.
 Basic data types like Integer, Float, Character, and Boolean come under the
Primitive Data Structures.
 These data types are also called Simple data types, as they contain characters
that can't be divided further

Non-Primitive Data Structures


 Non-Primitive Data Structures are those data structures derived from
Primitive Data Structures.
 These data structures can't be manipulated or operated directly by machine-
level instructions.
 The focus of these data structures is on forming a set of data elements that is
either homogeneous (same data type) or heterogeneous (different data
types).
 Based on the structure and arrangement of data, we can divide these data
structures into two sub-categories -

 Linear Data Structures


 Non-Linear Data Structures

 Linear Data Structures


A data structure that preserves a linear connection among its data elements is known
as a Linear Data Structure. The arrangement of the data is done linearly, where each
element consists of the successors and predecessors except the first and the last data
element. However, it is not necessarily true in the case of memory, as the
arrangement may not be sequential.
Based on memory allocation, the Linear Data Structures are further classified into two
types:

1. Static Data Structures: The data structures having a fixed size are known as
Static Data Structures. The memory for these data structures is allocated at the
compiler time, and their size cannot be changed by the user after being
compiled; however, the data stored in them can be altered.
The Array is the best example of the Static Data Structure as they have a fixed
size, and its data can be modified later.
2. Dynamic Data Structures: The data structures having a dynamic size are
known as Dynamic Data Structures. The memory of these data structures is
allocated at the run time, and their size varies during the run time of the code.
Moreover, the user can change the size as well as t he data elements stored in
these data structures at the run time of the code.
Linked Lists, Stacks, and Queues are common examples of dynamic data
structures

Types of Linear Data Structures


1. Arrays: An Array is a data structure used to collect multiple data elements of the
same data type into one variable. An Array is a list of elements where each element
has a unique place in the list. The data elements of the array share the same variable
name; however, each carries a different index number called a subscript. We can
access any data element from the list with the help of its location in the list. Thus, the
key feature of the arrays to understand is that the data is stored in contiguous
memory locations, making it possible for the users to traverse through the data
elements of the array using their respective indexes.

Arrays can be classified into different types:


1. One-Dimensional Array: An Array with only one row of data elements is
known as a One-Dimensional Array. It is stored in ascending storage location.
2. Two-Dimensional Array: An Array consisting of multiple rows and columns of
data elements is called a Two-Dimensional Array. It is also known as a Matrix.
3. Multidimensional Array: We can define Multidimensional Array as an Array of
Arrays. Multidimensional Arrays are not bounded to two indices or two
dimensions as they can include as many indices are per the need.
Some Applications of Array:
1. We can store a list of data elements belonging to the same data type.
2. Array acts as an auxiliary storage for other data structures.
3. The array also helps store data elements of a binary tree of the fixed count.
4. Array also acts as storage of matrices.

2. Linked Lists:

A Linked List is another example of a linear data structure used to store a collection
of data elements dynamically. Data elements in this data structure are represented by
the Nodes connected using links or pointers. Each node contains two fields, the
information field consists of the actual data, and the pointer field consists of the
address of the subsequent nodes in the list. The pointer of the last node of the linked
list consists of a null pointer, as it points to nothing. Unlike the Arrays, the user can
dynamically adjust the size of a Linked List as per the requirements.

Linked Lists can be classified into different types:


1. Singly Linked List: A Singly Linked List is the most common type of Linked
List. Each node has data and a pointer field containing an address to the next
node.
2. Doubly Linked List: A Doubly Linked List consists of an information field and
two pointer fields. The information field contains the data. The first pointer field
contains an address of the previous node, whereas another pointer field
contains a reference to the next node. Thus, we can go in both directions
(backward as well as forward).
3. Circular Linked List: The Circular Linked List is similar to the Singly Linked
List. The only key difference is that the last node contains the address of the
first node, forming a circular loop in the Circular Linked List.

Some Applications of Linked Lists:


1. The Linked Lists help us implement stacks, queues, binary trees, and graphs of
predefined size.
2. We can also implement Operating System's function for dynamic memory
management.
3. Linked Lists also allow polynomial implementation for mathe matical operations.
4. We can use Circular Linked List to implement Operating Systems or application
functions that Round Robin execution of tasks.
5. Circular Linked List is also helpful in a Slide Show where a user requires to go
back to the first slide after the last slide is presented.
6. Doubly Linked List is utilized to implement forward and backward buttons in a
browser to move forward and backward in the opened pages of a website.
3. Stacks: A Stack is a Linear Data Structure that follows the LIFO (Last In, First
Out) principle that allows operations like insertion and deletion from one end of the
Stack, i.e., Top. Stacks can be implemented with the help of contiguous memory, an
Array, and non-contiguous memory, a Linked List. Real-life examples of Stacks are
piles of books, a deck of cards, piles of money, and many more.
The primary operations in the Stack are as follows:
1. Push: Operation to insert a new element in the Stack is termed as Push
Operation.
2. Pop: Operation to remove or delete elements from the Stack is termed as Pop
Operation.

Some Applications of Stacks:


a) The Stack is used as a Temporary Storage Structure for recursive operations.
b) Stack is also utilized as Auxiliary Storage Structure for function calls, nested
operations, and deferred/postponed functions.
c) We can manage function calls using Stacks.
d) Stacks are also utilized to evaluate the arithmetic expressions in different
programming languages.
e) Stacks are also helpful in converting infix expressions to postfix expressions.
f) Stacks allow us to check the expression's syntax in the programming
environment.
g) We can match parenthesis using Stacks.
h) Stacks can be used to reverse a String.
i) Stacks are helpful in solving problems based on backtracking.
j) We can use Stacks in depth-first search in graph and tree traversal.
k) Stacks are also used in Operating System functions.
l) Stacks are also used in UNDO and REDO functions in an edit.

4. Queues: A Queue is a linear data structure similar to a Stack with some


limitations on the insertion and deletion of the elements. The insertion of an element
in a Queue is done at one end, and the removal is done at another or opposite end.
Thus, we can conclude that the Queue data structure follows FIFO (First In, First Out)
principle to manipulate the data elements. Implementat ion of Queues can be done
using Arrays, Linked Lists, or Stacks. Some real -life examples of Queues are a line at
the ticket counter, an escalator, a car wash, and many more.

The above image is a real-life illustration of a movie ticket counter that can help us
understand the Queue where the customer who comes first is always served first. The
customer arriving last will undoubtedly be served last. Both ends of the Queue are
open and can execute different operations. Another example is a food court line
where the customer is inserted from the rear end while the customer is removed at
the front end after providing the service they asked for.

The following are the primary operations of the Queue:


1. Enqueue: The insertion or Addition of some data elements to the Queue is
called Enqueue. The element insertion is always done with the help of the rear
pointer.
2. Dequeue: Deleting or removing data elements from the Queue is termed
Dequeue. The deletion of the element is always done with the help of the front
pointer.

Some Applications of Queues:


1. Queues are generally used in the breadth search operation in Graphs.
2. Queues are also used in Job Scheduler Operations of Operating Systems, like a
keyboard buffer queue to store the keys pressed by users and a print buffer
queue to store the documents printed by the printer.
3. Queues are responsible for CPU scheduling, Job scheduling, and Disk
Scheduling.
4. Priority Queues are utilized in file-downloading operations in a browser.
5. Queues are also used to transfer data between peripheral devices and the CPU.
6. Queues are also responsible for handling interrupts generated by the User
Applications for the CPU.

 Non-Linear Data Structures


Non-Linear Data Structures are data structures where the data elements are not
arranged in sequential order. Here, the insertion and removal of data are not feasible
in a linear manner. There exists a hierarchical relationship between the individual
data items.

Types of Non-Linear Data Structures


1. Trees: A Tree is a Non-Linear Data Structure and a hierarchy containing a
collection of nodes such that each node of the tree stores a value and a list o f
references to other nodes (children).
The Tree data structure is a specialized method to arrange and collect data in the
computer to be utilized more effectively. It contains a central node, structural nodes,
and sub-nodes connected via edges. We can also say that the tree data structure
consists of roots, branches, and leaves connected.

Trees can be classified into different types:


1. Binary Tree: A Tree data structure where each parent node can have at most
two children is termed a Binary Tree.
2. Binary Search Tree: A Binary Search Tree is a Tree data structure where we
can easily maintain a sorted list of numbers.
3. AVL Tree: An AVL Tree is a self-balancing Binary Search Tree where each node
maintains extra information known as a Balance Factor whose value is either -1,
0, or +1.
4. B-Tree: A B-Tree is a special type of self-balancing Binary Search Tree where
each node consists of multiple keys and can have more than two children.
Some Applications of Trees:
1. Trees implement hierarchical structures in computer systems like directories
and file systems.
2. Trees are also used to implement the navigation structure of a website.
3. We can generate code like Huffman's code using Trees.
4. Trees are also helpful in decision-making in Gaming applications.
5. Trees are responsible for implementing priority queues for priority-based OS
scheduling functions.
6. Trees are also responsible for parsing expressions and statements in the
compilers of different programming languages.
7. We can use Trees to store data keys for indexing for Database Management
System (DBMS).
8. Spanning Trees allows us to route decisions in Computer and Communications
Networks.
9. Trees are also used in the path-finding algorithm implemented in Artificial
Intelligence (AI), Robotics, and Video Games Applications.

2. Graphs: A Graph is another example of a Non-Linear Data Structure comprising a


finite number of nodes or vertices and the edges connecting them. The Graphs are
utilized to address problems of the real world in which it denotes the problem area as
a network such as social networks, circuit networks, and telephone networks. For
instance, the nodes or vertices of a Graph can represent a single user in a telephone
network, while the edges represent the link between them via telephone.

The Graph data structure, G is considered a mathematical structure comprised of a


set of vertices, V and a set of edges, E as shown below:

G = (V, E)

The above figure represents a Graph having seven vertices A, B, C, D, E, F, G, and


ten edges [A, B], [A, C], [B, C], [B, D], [B, E], [C, D], [D, E], [D, F], [E, F], and [E,
G].

Depending upon the position of the vertices and edges, the Graphs can be
classified into different types:
a) Null Graph: A Graph with an empty set of edges is termed a Null Graph.
b) Trivial Graph: A Graph having only one vertex is termed a Trivial Graph.
c) Simple Graph: A Graph with neither self-loops nor multiple edges is known
as a Simple Graph.
d) Multi Graph: A Graph is said to be Multi if it consists of multiple edges but
no self-loops.
e) Pseudo Graph: A Graph with self-loops and multiple edges is termed a
Pseudo Graph.
f) Non-Directed Graph: A Graph consisting of non-directed edges is known as
a Non-Directed Graph.
g) Directed Graph: A Graph consisting of the directed edges between the
vertices is known as a Directed Graph.
h) Connected Graph: A Graph with at least a single path between every pair
of vertices is termed a Connected Graph.
i) Disconnected Graph: A Graph where there does not exist any path
between at least one pair of vertices is termed a Disconnected Graph.
j) Regular Graph: A Graph where all vertices have the same degree is termed
a Regular Graph.
k) Complete Graph: A Graph in which all vertices have an edge between every
pair of vertices is known as a Complete Graph.
l) Cycle Graph: A Graph is said to be a Cycle if it has at least three vertices
and edges that form a cycle.
m) Cyclic Graph: A Graph is said to be Cyclic if and only if at least one cycle
exists.
n) Acyclic Graph: A Graph having zero cycles is termed an Acyclic Graph.
o) Finite Graph: A Graph with a finite number of vertices and edges is known
as a Finite Graph.
p) Infinite Graph: A Graph with an infinite number of vertices and edges is
known as an Infinite Graph.
q) Bipartite Graph: A Graph where the vertices can be divided into
independent sets A and B, and all the vertices of set A should only be
connected to the vertices present in set B with some edges is termed a
Bipartite Graph.
r) Planar Graph: A Graph is said to be a Planar if we can draw it in a single
plane with two edges intersecting each other.
s) Euler Graph: A Graph is said to be Euler if and only if all the vertices are
even degrees.
t) Hamiltonian Graph: A Connected Graph consisting of a Hamiltonian circuit
is known as a Hamiltonian Graph.

Some Applications of Graphs:


1. Graphs help us represent routes and networks in transportation, travel, and
communication applications.
2. Graphs are used to display routes in GPS.
3. Graphs also help us represent the interconnections in social networks a nd other
network-based applications.
4. Graphs are utilized in mapping applications.
5. Graphs are responsible for the representation of user preference in e -commerce
applications.
6. Graphs are also used in Utility networks in order to identify the problems posed
to local or municipal corporations.
7. Graphs also help to manage the utilization and availability of resources in an
organization.
8. Graphs are also used to make document link maps of the websites in order to
display the connectivity between the pages through hyperlinks.
9. Graphs are also used in robotic motions and neural networks

Basic Operations of Data Structures:


In the following section, we will discuss the different types of operations that we can
perform to manipulate data in every data structure:

1. Traversal: Traversing a data structure means accessing each data element


exactly once so it can be administered. For example, traversing is required
while printing the names of all the employees in a department.
2. Search: Search is another data structure operation which means to find the
location of one or more data elements that meet certain constraints. Such a
data element may or may not be present in the given set of data elements. For
example, we can use the search operation to find the names of all the
employees who have the experience of more than 5 years.
3. Insertion: Insertion means inserting or adding new data elements to the
collection. For example, we can use the insertion operation to add the details of
a new employee the company has recently hired.
4. Deletion: Deletion means to remove or delete a specific data element from the
given list of data elements. For example, we can use the deleting operation to
delete the name of an employee who has left the job.
5. Sorting: Sorting means to arrange the data elements in either Ascending or
Descending order depending on the type of application. For example, we can
use the sorting operation to arrange the names of employees in a department
in alphabetical order or estimate the top three performers of the month by
arranging the performance of the employees in descending order and extracting
the details of the top three.
6. Merge: Merge means to combine data elements of two sorted lists in order to
form a single list of sorted data elements.
7. Create: Create is an operation used to reserve memory for the data elements
of the program. We can perform this operation using a declaration statement.
The creation of data structure can take place either during the following:
1. Compile-time
2. Run-time
For example, the malloc() for memory allocation, is a standard library
function in C used to dynamically allocate a block of memory during
runtime.
8. Selection: Selection means selecting a particular data from the available data.
We can select any particular data by specifying conditions inside the loop.
9. Update: The Update operation allows us to update or modify the data in the
data structure. We can also update any particular data by specifying some
conditions inside the loop, like the Selection operation.
10. Splitting: The Splitting operation allows us to divide data into various
subparts decreasing the overall process completion time.
Abstract Data Type:
An Abstract Data Type (ADT) is a model for data types where the data type's behavior
is defined by a set of values and a set of operations. The key aspect of ADTs is that
they provide a way to encapsulate data and operations on that data into a single unit,
hiding the implementation details from the user1. This abstraction allows users to
work with data structures without needing to know how they are implemented.

Key Principles of ADTs


1. Abstraction: The user does not need to know the implementation details of the
data structure, only the operations that can be performed on it 1.
2. Encapsulation: ADTs hide the internal details of the data and provide a public
interface for users to interact with the data 1.
3. Data Structure Independence: ADTs can be implemented using different data
structures, such as arrays or linked lists, without affecting the functionality of the
ADT.
4. 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.
5. Modularity: ADTs can be combined with other ADTs to form larger, more complex
data structures. This allows for greater flexibility and modularity in programming.

The user of data type does not need to know how that data type is implemented, for
example, we have been using Primitive values like int, float, and 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
 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
 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
 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.

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.

Algorithm
An algorithm is a process or a set of rules required to perform calculations or some
other problem-solving operations especially by a computer. The formal definition of
an algorithm is that it contains the finite set of instructions which are being carried in
a specific order to perform the specific task. It is not the complete program or code; it
is just a solution (logic) of a problem, which can be represented either as an informal
description using a Flowchart or Pseudo code.

Characteristics of an Algorithm
o Input: An algorithm has some input values. We can pass 0 or some input value
to an algorithm.
o Output: We will get 1 or more output at the end of an algorithm.
o Unambiguity: An algorithm should be unambiguous which means that the
instructions in an algorithm should be clear and simple.
o Finiteness: An algorithm should have finiteness. Here, finiteness means that
the algorithm should contain a limited number of instructions, i.e., the
instructions should be countable.
o Effectiveness: An algorithm should be effective as each instruction in an
algorithm affects the overall process.
o Language independent: An algorithm must be language-independent so that
the instructions in an algorithm can be implemented in any of the languages
with the same output.
Dataflow of an Algorithm
o 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.
o Algorithm: An algorithm will be designed for a problem which is a step by step
procedure.
o Input: After designing an algorithm, the required and the desired inputs are
provided to the algorithm.
o Processing unit: The input will be given to the processing unit, and the
processing unit will produce the desired output.
o Output: The output is the outcome or the result of the program.

Why do we need Algorithms?


We need algorithms because of the following reasons:

o Scalability: It helps us to understand the scalability. When we have a big real -


world problem, we need to scale it down into small-small steps to easily analyze
the problem.
o Performance: The real-world is not easily broken down into smaller steps. If
the problem can be easily broken into smaller steps means that the problem is
feasible.
The following are the factors that we need to consider for designing an
algorithm:

o Modularity: If any problem is given and we can break that problem into small -
small modules or small-small steps, which is a basic definition of an algorithm,
it means that this feature has been perfectly designed for the algorithm.
o Correctness: The correctness of an algorithm is defined as when the given
inputs produce the desired output, which means that the algorithm has been
designed algorithm. The analysis of an algorithm has been done correctly.
o Maintainability: Here, maintainability means that the algorithm should be
designed in a very simple structured way so that when we redefine the
algorithm, no major change will be done in the algorithm.
o Functionality: It considers various logical steps to solve the real-world
problem.
o Robustness: Robustness means that how an algorithm can clearly define our
problem.
o User-friendly: If the algorithm is not user-friendly, then the designer will not
be able to explain it to the programmer.
o Simplicity: If the algorithm is simple then it is easy to understand.
o Extensibility: If any other algorithm designer or programmer wants to use
your algorithm then it should be extensible.

The major categories of algorithms are given below:


o Sort: Algorithm developed for sorting the items in a certain order.
o Search: Algorithm developed for searching the items inside a data structure.
o Delete: Algorithm developed for deleting the existing element from the data
structure.
o Insert: Algorithm developed for inserting an item inside a data structure.
o Update: Algorithm developed for updating the existing element inside a data
structure.

Algorithm Analysis
The algorithm can be analyzed in two levels, i.e., first is before creating the
algorithm, and second is after creating the algorithm. The following are the two
analysis of an algorithm:

o Priori Analysis: Here, priori analysis is the theoretical analysis of an algorithm


which is done before implementing the algorithm. Various factors can be
considered before implementing the algorithm like processor speed, which has
no effect on the implementation part.
o Posterior Analysis: Here, posterior analysis is a practical analysis of an
algorithm. The practical analysis is achieved by implementing the algorithm
using any programming language. This analysis basically evaluate that how
much running time and space taken by the algorithm.
Algorithm Complexity
The performance of the algorithm can be measured in two factors:

o Time complexity: The time complexity of an algorithm is the amount of time


required to complete the execution. 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. The time complexity is mainly calculated by
counting the number of steps to finish the execution. Let's understand the time
complexity through an example.
1. sum=0;
2. // Suppose we have to calculate the sum of n numbers.
3. for i=1 to n
4. sum=sum+i;
5. // when the loop ends then sum holds the sum of the n numbers
6. return sum;
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. 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.

o Space complexity: 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.
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.

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.

Time Complexity or running time for performing the operations?


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.
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
The commonly used asymptotic notations used for calculating the running time
complexity of an algorithm is given below:
o Big oh Notation (?)
o Omega Notation (Ω)
o Theta Notation (θ)

Big oh Notation (O)


o Big O notation is an asymptotic notation that measures the performance of an
algorithm by simply providing the order of growth of the function.
o This notation provides an upper bound on a function which ensures that the
function never grows faster than the upper bound. So, it gives the least upper
bound on a function so that the function never grows faster than this upper
bound.
It is the formal way to express the upper boundary of an algorithm running time. It
measures the worst case of time complexity or the algorithm's longest amount of
time to complete its operation. It is represented as shown below:

For example:
If f(n) and g(n) are the two functions defined for positive integers,
then f(n) = O(g(n)) as f(n) is big oh of g(n) or f(n) is on the order of g(n)) if
there exists constants c and no such that:
f(n)≤c.g(n) for all n≥no
This implies that f(n) does not grow faster than g(n), or g(n) is an upper bound on
the function f(n). In this case, we are calculating the growth rate of the function
which eventually calculates the worst time complexity of a function, i.e., how worst
an algorithm can perform.

Let's understand through examples


f(n)=2n+3 , g(n)=n
Now, we have to find Is f(n)=O(g(n))?
To check f(n)=O(g(n)), it must satisfy the given condition:
f(n)<=c.g(n)
First, we will replace f(n) by 2n+3 and g(n) by n.
2n+3 <= c.n
Let's assume c=5, n=1 then
2*1+3<=5*1
5<=5
For n=1, the above condition is true.
If n=2
2*2+3<=5*2
7<=10
For n=2, the above condition is true.
We know that for any value of n, it will satisfy the above condition, i.e., 2n+3<=c.n.
If the value of c is equal to 5, then it will satisfy the condition 2n+3<=c.n. We can
take any value of n starting from 1, it will always satisfy. The refore, we can say that
for some constants c and for some constants n0, it will always satisfy 2n+3<=c.n. As
it is satisfying the above condition, so f(n) is big oh of g(n) or we can say that f(n)
grows linearly. Therefore, it concludes that c.g(n) is the upper bound of the f(n). It
can be represented graphically as:

The idea of using big o notation is to give an upper bound of a particular function, and
eventually it leads to give a worst-time complexity. It provides an assurance that a
particular function does not behave suddenly as a quadratic or a cubic fashion, it just
behaves in a linear manner in a worst-case.

Omega Notation (Ω)


o It basically describes the best-case scenario which is opposite to the big o
notation.
o It is the formal way to represent the lower bound of an algorithm's running
time. It measures the best amount of time an algorithm can possibly take t o
complete or the best-case time complexity.
o It determines what is the fastest time that an algorithm can run.

If we required that an algorithm takes at least certain amount of time without using
an upper bound, we use big- Ω notation i.e. the Greek letter "omega". It is used to
bound the growth of running time for large input size.
If f(n) and g(n) are the two functions defined for positive integers,
then f(n) = Ω (g(n)) as f(n) is Omega of g(n) or f(n) is on the order of g(n)) if
there exists constants c and no such that:
f(n)>=c.g(n) for all n≥no and c>0
Let's consider a simple example.
If f(n) = 2n+3, g(n) = n,
Is f(n)= Ω (g(n))?
It must satisfy the condition:
f(n)>=c.g(n)
To check the above condition, we first replace f(n) by 2n+3 and g(n) by n.
2n+3>=c*n
Suppose c=1
2n+3>=n (This equation will be true for any value of n starting from 1).
Therefore, it is proved that g(n) is big omega of 2n+3 function.

As we can see in the above figure that g(n) function is the lower bound of the f(n)
function when the value of c is equal to 1. Therefore, this notation gives the fastest
running time. But, we are not more interested in finding the fastest running time, we
are interested in calculating the worst-case scenarios because we want to check our
algorithm for larger input that what is the worst time that it will take so that we can
take further decision in the further process.

Theta Notation (θ)


o The theta notation mainly describes the average case scenarios.
o It represents the realistic time complexity of an algorithm. Every time, an
algorithm does not perform worst or best, in real-world problems, algorithms
mainly fluctuate between the worst-case and best-case, and this gives us the
average case of the algorithm.
o Big theta is mainly used when the value of worst-case and the best-case is
same.
o It is the formal way to express both the upper bound and lower bound of an
algorithm running time.
Let's understand the big theta notation mathematically:
Let f(n) and g(n) be the functions of n where n is the steps required to execute the
program then:
f(n)= θg(n)
The above condition is satisfied only if when
c1.g(n)<=f(n)<=c2.g(n)
where the function is bounded by two limits, i.e., upper and lower limit, and f(n)
comes in between. The condition f(n)= θg(n) will be true if and only if c1.g(n) is less
than or equal to f(n) and c2.g(n) is greater than or equal to f(n). The graphical
representation of theta notation is given below:

Let's consider the same example where


f(n)=2n+3
g(n)=n
As c1.g(n) should be less than f(n) so c1 has to be 1 whereas c2.g(n) should be
greater than f(n) so c2 is equal to 5. The c1.g(n) is the lower limit of the of the f(n)
while c2.g(n) is the upper limit of the f(n).
c1.g(n)<=f(n)<=c2.g(n)
Replace g(n) by n and f(n) by 2n+3
c1.n <=2n+3<=c2.n
if c1=1, c2=2, n=1
1*1 <=2*1+3 <=2*1
1 <= 5 <= 2 // for n=1, it satisfies the condition c1.g(n)<=f(n)<=c2.g(n)
If n=2
1*2<=2*2+3<=2*2
2<=7<=4 // for n=2, it satisfies the condition c1.g(n)<=f(n)<=c2.g(n)
Therefore, we can say that for any value of n, it satisfies the condition
c1.g(n)<=f(n)<=c2.g(n). Hence, it is proved that f(n) is big theta of g(n). So, this is
the average-case scenario which provides the realistic time complexity.
Types of Algorithms
1-Search Algorithm
2-Sort Algorithm

Search Algorithm
On each day, we search for something in our day to day life. Similarly, with the case
of computer, huge data is stored in a computer that whenever the user asks for any
data then the computer searches for that data in the memory and provides that data
to the user. There are mainly two techniques available to search the data in an array:
o Linear search
o Binary search

Linear Search
Linear search is a very simple algorithm that starts searching for an element or a
value from the beginning of an array until the required element is not found. It
compares the element to be searched with all the elements in an array, if the match
is found, then it returns the index of the element. This algorithm can be implemented
on the unsorted list.
Binary Search
A Binary algorithm is the simplest algorithm that searches the element very quickly.
It is used to search the element from the sorted list. The elements must be stored in
sequential order or the sorted manner to implement the binary algorithm. Binary
search cannot be implemented if the elements are stored in a random manner. It is
used to find the middle element of the list. In this technique we have arranged the
data in particular order before searching and compare the searching element with
middle element of the list and have to perform searching accordingly.
Sorting Algorithms
Sorting algorithms are used to rearrange the elements in an array or a given data
structure either in an ascending or descending order. The comparison operator
decides the new order of the elements.
Why do we need a sorting algorithm?
o An efficient sorting algorithm is required for optimizing the efficiency of other
algorithms like binary search algorithm as a binary search algorithm requires an
array to be sorted in a particular order, mainly in ascending order.
o It produces information in a sorted order, which is a human-readable format.
o Searching a particular element in a sorted list is faster than the unsorted list.

Characteristics of Sorting Algorithms:


 Time Complexity: Time complexity, a measure of how long it takes to run an
algorithm, is used to categorize sorting algorithms. The worst-case, average-
case, and best-case performance of a sorting algorithm can be used to quantify
the time complexity of the process.
 Auxiliary Space: This is the amount of extra space (apart from input array)
needed to sort. For example, Merge Sort requires O(n) and Insertion Sort O(1)
auxiliary space
 Stability: A sorting algorithm is said to be stable if the relative order of equal
elements is preserved after sorting. This is important in certain applications
where the original order of equal elements must be maintained.
 In-Place Sorting: An in-place sorting algorithm is one that does not require
additional memory to sort the data. This is important when the available memory
is limited or when the data cannot be moved.
 Adaptively: An adaptive sorting algorithm is one that takes advantage of pre-
existing order in the data to improve performance. For example insertion sort
takes time proportional to number of inversions in the input array.

Algorithm Description Time Space


Complexity Complexity

Compares nearby elements, swaps


Bubble
any that are out of order, and repeats O(n^2) O(1)
Sort
until the array is sorted.

Divides the input array into two


sections: one that is sorted and one
Selection that is not. It does this by repeatedly
O(n^2) O(1)
Sort choosing the smallest element in the
unsorted zone and moving it to the
sorted region.

By continuously inserting each


unsorted element into the appropriate
Insertion
location within the sorted sub array, O(n^2) O(1)
Sort
the final sorted array is built one
element at a time.
Merge Sort splits the input array in
half, sorts each half iteratively, and
Merge Sort O(n log n) O(n)
then combines the two sorted halves
to create the final sorted array.

Selects a pivot element and divides


the array so that everything that is
smaller than the pivot is put in front
Quick Sort of it, and everything that is bigger O(n log n) O(log n)
than the pivot is put in back. The
subarrays prior to and following the
pivot are then sorted recursively.

Creates a max-heap from the input


array, removes the highest element
Heap Sort from the heap periodically, swaps it O(n log n) O(1)
for the last element, and modifies the
heap until the array is sorted.

creates a count array to keep track of


how many distinct elements there are
Counting in the input array. Then it determines
O(n + k) O(n + k)
Sort the position of each element by
calculating the cumulative sum of
counts.

Divides the input array into a set of


buckets, where each bucket
represents a range of values. It
Bucket distributes elements into the O(n^2) or O(n
O(n)
Sort respective buckets based on their + k)
values. Then, each bucket is sorted
separately, usually using a different
sorting method.

Sorts elements by processing


individual digits or characters of the
elements from the least significant
Radix Sort O(d * (n + k)) O(n + k)
digit (LSD) to the most significant
digit (MSD). As a subroutine, it may
employ a reliable sorting algorithm.

Sorting Algorithms:
 Selection Sort
 Bubble Sort
 Insertion Sort
 Merge Sort
 Quick Sort
 Heap Sort
 Counting Sort
 Radix Sort
 Bucket Sort
Pointer
Pointer is used to points the address of the value stored anywhere in the computer
memory. To obtain the value stored at the location is known as dereferencing the
pointer. Pointer improves the performance for repetitive process such as:
o Traversing String
o Lookup Tables
o Control Tables
o Tree Structures

Pointer Details
o Pointer arithmetic: There are four arithmetic operators that can be used in
pointers: ++, --, +, -
o Array of pointers: You can define arrays to hold a number of pointers.
o Pointer to pointer: C allows you to have pointer on a pointer and so on.
o Passing pointers to functions in C: Passing an argument by reference or by
address enable the passed argument to be changed in the calling function by
the called function.
o Return pointer from functions in C: C allows a function to return a pointer to
the local variable, static variable and dynamically allocated memory as well.
Structure
A structure is a composite data type that defines a grouped list of variables that are
to be placed under one name in a block of memory. It allows different variables to be
accessed by using a single pointer to the structure.

Syntax
struct structure_name
{
data_type member1;
data_type member2;
.
.
data_type memeber;
}

Program
#include<stdio.h>
#include<conio.h>
void main( )
{
struct employee
{
int id ;
float salary ;
int mobile ;
};
struct employee e1,e2,e3 ;
clrscr();
printf ("\nEnter ids, salary & mobile no. of 3 employee\n"
scanf ("%d %f %d", &e1.id, &e1.salary, &e1.mobile);
scanf ("%d%f %d", &e2.id, &e2.salary, &e2.mobile);
scanf ("%d %f %d", &e3.id, &e3.salary, &e3.mobile);
printf ("\n Entered Result ");
printf ("\n%d %f %d", e1.id, e1.salary, e1.mobile);
printf ("\n%d%f %d", e2.id, e2.salary, e2.mobile);
printf ("\n%d %f %d", e3.id, e3.salary, e3.mobile);
getch();
}

Advantages
o It can hold variables of different data types.
o We can create objects containing different types of attributes.
o It allows us to re-use the data layout across programs.
o It is used to implement other data structures like linked lists, stacks, queues,
trees, graphs etc.

Array
An array is a fundamental data structure in computer science that stores a collection
of elements in contiguous memory locations. Each element in an array is of the same
data type and can be accessed using an index, starting from 0. Arrays are widely
used for organizing and manipulating data due to their simplicity and efficiency.

Syntax:
data_type array_name[array_size]={elements separated by commas}
or,
data_type array_name[array_size];

Definition and Properties


An array is a collection of items stored at contiguous memory locations. This a llows
for efficient access to elements using indices. Each element in an array is of the same
data type and carries the same size. Arrays can be one-dimensional or
multidimensional, with the latter storing multiple rows of element s.

Operations on Arrays
 Traversal: Visiting each element of an array in a specific order.
 Insertion: Adding a new element to an array at a specific index.
 Deletion: Removing an element from an array at a specific index.
 Searching: Finding the index of an element in an array.
 Update: Modifying an existing element at a specific index.

Applications
Arrays are used in a wide variety of applications, including:
 Storing data for processing
 Implementing data structures such as stacks and queues
 Representing data in tables and matrices
 Creating dynamic data structures such as linked lists and trees.

Advantages:
 Easy to remember the name of all elements.
 Simple traversal process.
 Direct access to any element using the index.
Disadvantages:
 Homogeneous nature: Only elements of the same data type can be stored.
 Static memory allocation: The size of an array cannot be altered once declared.
 Potential memory wastage if fewer elements are stored than the declared size.

Array Representation:
Arrays are represented as a collection of buckets where each bucket stores one
element. These buckets are indexed from '0' to 'n-1', where n is the size of that
particular array. For example, an array with size 10 will have buckets indexed from 0
to 9.

This indexing will be similar for the multidimensional arrays as well. If it is a 2-


dimensional array, it will have sub-buckets in each bucket. Then it will be indexed as
array_name[m][n], where m and n are the sizes of each level in the array.

Array - Insertion Operation


In the insertion operation, we are adding one or more elements to the array. Based
on the requirement, a new element can be added at the beginning, end, or any given
index of array. This is done using input statements of the programming languages.
Algorithm:
Following is an algorithm to insert elements into a Linear Array until we reach the end
of the array:

Example
int main() {
int LA[3] = {}, i;
printf("Array Before Insertion:\n");
for(i = 0; i < 3; i++)
printf("LA[%d] = %d \n", i, LA[i]);
printf("Inserting Elements.. \n");
printf("The array elements after insertion :\n"); // prints array values
for(i = 0; i < 3; i++) {
LA[i] = i + 2;
printf("LA[%d] = %d \n", i, LA[i]);
}
return 0; }

Array - Deletion Operation


In this array operation, we delete an element from the particular index of an array.
This deletion operation takes place as we assign the value in the consequent index to
the current index.
Algorithm:
Consider LA is a linear array with N elements and K is a positive integer such that
K<=N. Following is the algorithm to delete an element available at the Kth position of
LA.
1. Start
2. Set J = K
3. Repeat steps 4 and 5 while J < N
4. Set LA[J] = LA[J + 1]
5. Set J = J+1
6. Set N = N-1
7. Stop

Example
void main(){
int LA[] = {1,3,5};
int n = 3;
int i;
printf("The original array elements are :\n");
for(i = 0; i<n; i++)
printf("LA[%d] = %d \n", i, LA[i]);
for(i = 1; i<n; i++) {
LA[i] = LA[i+1];
n = n - 1;
}
printf("The array elements after deletion :\n");
for(i = 0; i<n; i++)
printf("LA[%d] = %d \n", i, LA[i]); }

Array - Search Operation


Searching an element in the array using a key; The key element sequentially
compares every value in the array to check if the key is present in the array or not.

Algorithm:
Consider LA is a linear array with N elements and K is a positive integer such that
K<=N. Following is the algorithm to find an element with a value of ITEM using
sequential search.
1. Start
2. Set J = 0
3. Repeat steps 4 and 5 while J < N
4. IF LA[J] is equal ITEM THEN GOTO STEP 6
5. Set J = J +1
6. PRINT J, ITEM
7. Stop

Example
void main(){
int LA[] = {1,3,5,7,8};
int item = 5, n = 5;
int i = 0, j = 0;
printf("The original array elements are :\n");
for(i = 0; i<n; i++) {
printf("LA[%d] = %d \n", i, LA[i]);
}
for(i = 0; i<n; i++) {
if( LA[i] == item ) {
printf("Found element %d at position %d\n", item, i+1); }}}

Array - Traversal Operation


This operation traverses through all the elements of an array. We use loop
statements to carry this out.

Algorithm:
Following is the algorithm to traverse through all the elements present in a Linear
Array
1. Start
2. Initialize an Array of certain size and data type.
3. Initialize another variable ‘i’ with 0.
4. Print the i’th value in the array and increment i.
5. Repeat Step 4 until the end of the array is reached.
6. End
Example
int main(){
int LA[] = {1,3,5,7,8};
int item = 10, k = 3, n = 5;
int i = 0, j = n;
printf("The original array elements are :\n");
for(i = 0; i<n; i++) {
printf("LA[%d] = %d \n", i, LA[i]); }}

Types of Array
Arrays can be classified based on their dimensions, which refer to the number of
indices required to access an element in the array. In this section, we will discuss t wo
types of arrays: single-dimensional arrays and multi-dimensional arrays.
1. Single Dimensional Array
2. Multi-Dimensional Array

1. Single Dimensional Array


A single-dimensional array is the simplest type of array that stores elements in a
linear sequence. It is also known as a one-dimensional array, and each element in the
array can be accessed using a single index. The index ranges from 0 to the length of
the array minus one.

Initialization of Single Dimensional Array


data_type array_name[array_size] = {element1, element2, element3, ......... ,
element N};
//Example
int evenNumbers[5] = {0, 2, 4, 6, 8};

2. Multi-Dimensional Array
A multi-dimensional array is an array that stores elements in a two-dimensional or
higher-dimensional structure. It is also known as a two-dimensional or higher-
dimensional array, and each element in the array can be accessed using multiple
indices. Multi-dimensional arrays can be of different dimensions, including two-
dimensional, three-dimensional, or higher-dimensional arrays.

Syntax for Multi-Dimensional Array Declaration


data_type
array_name[sizeof_1st_dimension][sizeof_2nd_dimension].....[sizeof_nth_dimension]
;
//Example
int array[5][4][6][7];

I. Two-Dimensional Array
A multidimensional array with two dimensions is known as a two-dimensional array. A
two-dimensional array is an array of arrays, where each element in the array is itself
an array. It is also known as a matrix, and can be used to represent tables, grids, or
other two-dimensional structures.
Syntax and Declaration of Two Dimensional Arrays
data_type array_name[sizeof_1st_dimension][sizeof_2nd_dimension];
//Example
int array2d[5][10];
II. Three-Dimensional Array
A three-dimensional array is a type of multi-dimensional array that stores elements in
a three-dimensional structure. It is often used to represent three-dimensional
structures such as cubes, rectangular prisms, or other geometric shapes. In a three -
dimensional array, each element is accessed using three indices, one for the depth or
"layer", one for the row, and one for the column.
Syntax for Declaration of Three Dimensional Arrays
data_type
array_name[sizeof_1st_dimension][sizeof_2nd_dimension][sizeof_3rd_dimension];
//Example
int array[5][10][15];

Calculation of address of element of 1-D, 2-D, and 3-D using


Row-major and Column-major order

1- Calculating 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.
To find the address of an element in an array the following formula is used-
Address of A[i] = B + i*w (when index starts with 0)
Address of A[i] = B + (i-1)*w (when index starts with 1)
Address of A[i] = B + (i – LB)*w (when index is other than (0, 1))
Where:
i = the index of the element whose address is to be found (not the value of the
element).
B = Base address of the array.
w = Storage size of one element in bytes.
LB = Lower bound of the index (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 bound (LB) = 1300, Size of each element (w)
= 2 bytes, Index of element (not value) = 1700
Formula used: Address of A[Index] = B + W * (Index – LB)
Address of A[1700] = 1020 + 2 * (1700 – 1300)
= 1020 + 2 * (400)
= 1020 + 800
Address of A[1700] = 1820

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


To find the address of any element in a 2-Dimensional array there are the following
two ways-
Row Major Order
Column Major Order

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.

To find the address of the element using row-major order uses the following formula:
Address of A[i][j] = B + (i*n+j)*w (when index starts with 0) Or
Address of A[i][j] = B + [(i-1)*n+(j-1)]*w (when index starts with 1) Or
Address of A[i][j] = B + w * [n(i – LR) + (j – LC)] (when index is other than (0,1))
Where:
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))
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

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:
Where:
Address of A[i][j] = B + (j*m+i)*w (when index starts with 0) Or
Address of A[i][j] = B + [(j-1)*m+(i-1)]*w (when index starts with 1) Or
Address of A[i][j] = B + w [(i – LR) +m (j – LC)] (when index is other than (0,1))

Address of A[i][j] = B + w [(i – LR) + m (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 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: 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

3- Calculating the address of any element in the 3-D array:


To find the address of any element in a 3-Dimensional array there are the following
two ways-
Row Major Order
Column Major Order

Row Major Order


To find the address of the element using row-major order use the following formula:
A[i][j][k] = B + w [(E1*L2 + E3)L3+E3]
Where:
B = Base address
w = Storage size of one element
L1,L2,L3 = Length of 3D array [L1= Upper bound –Lower bound of i, L2= Upper
bound –Lower bound of j, L3= Upper bound –Lower bound of k.]
E1,E2,E3 = Effective Index of 3D array [E1= i-Lower bound of i, E2= j-Lower bound
of j, E3= k-Lower bound of k.]

Column Major Order


To find the address of the element using column-major order use the following
formula:
A[i][j][k] = B + w [(E3*L2 + E2)L1+E1]
Where:
B = Base address
w = Storage size of one element
L1,L2,L3 = Length of 3D array [L1= Upper bound –Lower bound of i, L2= Upper
bound –Lower bound of j, L3= Upper bound –Lower bound of k.]
E1,E2,E3 = Effective Index of 3D array [E1= i-Lower bound of i, E2= j-Lower bound
of j, E3= k-Lower bound of k.]

Sparse Matrix and its representations


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 values,
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

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

Linked List
Linked List is a linear data structure which looks like a series of nodes, where
each node has two parts: data and next pointer. Unlike Arrays, Linked List
elements are not stored at a contiguous location. In the linked list there is a head
pointer, which points to the first element of the linked list, and if the list is empty
then it simply points to null or nothing.

Basic Terminologies of Linked List


 Head: The Head of a linked list is a pointer to the first node or reference of the
first node of linked list. This pointer marks the beginning of the linked list.
 Node: Linked List consists of a series of nodes where each node has two
parts: data and next pointer.
 Data: Data is the part of node which stores the information in the linked list.
 Next pointer: Next pointer is the part of the node which points to the next node
of the linked list.

Importance of Linked List


Here are a few advantages of a linked list that is listed below, it will help you
understand why it is necessary to know.
 Dynamic Data structure: The size of memory can be allocated or de-allocated
at run time based on the operation insertion or deletion.
 Ease of Insertion/Deletion: The insertion and deletion of elements are simpler
than arrays since no elements need to be shifted after insertion and deletion, just
the address needed to be updated.
 Efficient Memory Utilization: As we know Linked List is a dynamic data
structure the size increases or decreases as per the requirement so this avoids
the wastage of memory.
 Implementation: Various advanced data structures can be implemented using a
linked list like a stack, queue, graph, hash maps, etc.

Types of Linked List


1. Singly linked list
2. Doubly linked list
3. Circular linked list

1. Singly Linked List


Singly Linked List is a type of linked list where each node has two
parts: data and next pointer. The data part stores the information and the next
pointer points to the next node of the linked list. The next pointer of the last node
stores null as it is the last node of the linked list and there is no next node.
Traversal of items can be done in the forward direction only due to the linking of
every node to its next node.

// Singly linked list node in C


struct Node {
// Data field
int data;
// Pointer to the next node
struct Node* next;
};

Basic Operations on Singly Linked List


 Insertion: The insertion operation can be performed in three ways. They are as
follows:

o Inserting At the Beginning of the list


#include <stdio.h>
#include <stdlib.h>

// Define the structure for a node


struct Node {
int data;
struct Node* next;
};

// Function to insert an element at the beginning of the list


void insertAtBeginning(struct Node** head_ref, int new_data) {
// Allocate memory for the new node
struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));

// Assign data to the new node


new_node->data = new_data;
// Make the next of new node as head
new_node->next = *head_ref;

// Move the head to point to the new node


*head_ref = new_node;
}

// Function to print the linked list


void printList(struct Node* node) {
while (node != NULL) {
printf("%d -> ", node->data);
node = node->next;
}
printf("NULL\n");
}

int main() {
struct Node* head = NULL; // Initialize an empty list

// Insert elements at the beginning


insertAtBeginning(&head, 10);
insertAtBeginning(&head, 20);
insertAtBeginning(&head, 30);

// Print the list


printList(head);

return 0;
}
o Inserting At End of the list
#include <stdio.h>
#include <stdlib.h>

// Define the structure for a node


struct Node {
int data;
struct Node* next;
};

// Function to create a new node


struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}

// Function to insert a new node at the end of the list


void insertAtEnd(struct Node** head, int data) {
struct Node* newNode = createNode(data);

// If the list is empty


if (*head == NULL) {
*head = newNode;
return;
}

// Traverse to the end of the list


struct Node* temp = *head;
while (temp->next != NULL) {
temp = temp->next;
}

// Insert the new node at the end


temp->next = newNode;
}

// Function to print the list


void printList(struct Node* head) {
struct Node* temp = head;
while (temp != NULL) {
printf("%d -> ", temp->data);
temp = temp->next;
}
printf("NULL\n");
}

int main() {
struct Node* head = NULL;

// Inserting elements at the end


insertAtEnd(&head, 10);
insertAtEnd(&head, 20);
insertAtEnd(&head, 30);

// Printing the list


printList(head);

return 0;
}
o Inserting At Specific location in the list
#include <stdio.h>
#include <stdlib.h>

// Node structure
struct Node {
int data;
struct Node* next;
};

// Function to insert a node at a specific position


void insertAtPosition(struct Node** head, int data, int position) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
// Special case for head
if (position == 1) {
newNode->next = *head;
*head = newNode;
return;
}

struct Node* temp = *head;


for (int i = 1; i < position - 1 && temp != NULL; i++) {
temp = temp->next;
}

if (temp == NULL) {
printf("Position out of bounds\n");
return;
}

newNode->next = temp->next;
temp->next = newNode;
}

// Function to print the linked list


void printList(struct Node* node) {
while (node != NULL) {
printf("%d -> ", node->data);
node = node->next;
}
printf("NULL\n");
}

int main() {
struct Node* head = NULL;

// Insert at positions
insertAtPosition(&head, 10, 1); // Insert 10 at position 1
insertAtPosition(&head, 20, 2); // Insert 20 at position 2
insertAtPosition(&head, 30, 3); // Insert 30 at position 3
insertAtPosition(&head, 25, 4); // Insert 25 at position 4

// Print the linked list


printList(head);

return 0;
}
 Deletion: The deletion operation can be performed in three ways. They are as
follows:
o Deleting from the Beginning of the list
#include <stdio.h>
#include <stdlib.h>

// Structure of a linked list node


struct node {
int info;
struct node* next;
};

// Pointer to last node in list


struct node* last = NULL;

// Function to add a new node


// at the end of the list
void addatlast(int data)
{
// Initialize a new node
struct node* temp;
temp = (struct node*)malloc(sizeof(struct node));

// If the new node is the only


// node in the list
if (last == NULL) {
temp->info = data;
temp->next = temp;
last = temp;
}

// Else the new node will be the


// last node and will contain
// the reference of head node
else {
temp->info = data;
temp->next = last->next;
last->next = temp;
last = temp;
}
}

// Function to delete the first


// element of the list
void deletefirst()
{
struct node* temp;

// If list is empty
if (last == NULL)
printf("\nList is empty.\n");

// Else last node now contains


// reference of the second node
// in the list because the
// list is circular
else {
temp = last->next;
last->next = temp->next;
free(temp);
}
}

// Function to print the list


void viewList()
{
// If list is empty
if (last == NULL)
printf("\nList is empty\n");

// Else print the list


else {
struct node* temp;
temp = last->next;
do {
printf("\nData = %d", temp->info);
temp = temp->next;
} while (temp != last->next);
}
}

// Driver Code
int main()
{
// Initialize the list
addatlast(10);
addatlast(20);
addatlast(30);

printf("Before deletion:\n");
viewList();

// Function Call
deletefirst();

// Print list
printf("\n\nAfter deletion:\n");
viewList();

return 0;
}
o Deleting from the End of the list
o Deleting a Specific Node
 Traverse: This process displays the elements of a Single-linked list.
 Search: It is a process of determining and retrieving a specific node either from
the front, the end or anywhere in the list.

2. Doubly Linked List:


Doubly Linked List is a type of linked list where each node has three parts: data,
next pointer and previous pointer. The data part stores the information, the next
pointer points to the next node of the linked list and the previous pointer points to
the previous node of the linked list. The next pointer of the last node and the
previous pointer of the first node stores null. Traversal of items can be done in the
forward direction as well as backward direction due to the linking of every node to
its next node as well as the previous node.
// Doubly linked list node in C
struct Node {
// Data field
int data;
// Previous node
Node* prev;
// Next node
Node* next;
};

Operations on Doubly Linked List:


 Insertion: The insertion operation can be performed in three ways as follows:

o Inserting At the Beginning of the list


#include <stdio.h>
#include <stdlib.h>

// Node structure
struct Node {
int data;
struct Node* prev;
struct Node* next;
};

// Function to insert a node at the beginning


void insertAtBeginning(struct Node** head, int newData) {
// Allocate memory for new node
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = newData;
newNode->prev = NULL; // Previous of new node will be NULL
newNode->next = (*head); // Next of new node will be current head

// If the list is not empty, set the previous head's prev to new node
if (*head != NULL)
(*head)->prev = newNode;

// Move the head to point to the new node


*head = newNode;
}

// Function to print the list


void printList(struct Node* node) {
struct Node* last;
printf("Doubly Linked List: ");
while (node != NULL) {
printf("%d ", node->data);
last = node;
node = node->next;
}
printf("\n");
}

int main() {
struct Node* head = NULL;

// Inserting nodes at the beginning


insertAtBeginning(&head, 10);
insertAtBeginning(&head, 20);
insertAtBeginning(&head, 30);

// Printing the list


printList(head);

return 0;
}
o Inserting after a given node.
#include <stdio.h>
#include <stdlib.h>

// Define the structure for a doubly linked list node


struct Node {
int data;
struct Node* next;
struct Node* prev;
};

// Function to create a new node


struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
newNode->prev = NULL;
return newNode;
}

// Function to insert a node at a specific position


void insertAtPosition(struct Node** head, int data, int position) {
struct Node* newNode = createNode(data);
if (position == 1) { // Inserting at the head
newNode->next = *head;
if (*head != NULL) {
(*head)->prev = newNode;
}
*head = newNode;
return;
}
struct Node* temp = *head;
for (int i = 1; temp != NULL && i < position - 1; i++) {
temp = temp->next;
}

if (temp == NULL) {
printf("Position out of range\n");
return;
}

newNode->next = temp->next;
newNode->prev = temp;

if (temp->next != NULL) {
temp->next->prev = newNode;
}
temp->next = newNode;
}

// Function to print the list


void printList(struct Node* head) {
struct Node* temp = head;
while (temp != NULL) {
printf("%d ", temp->data);
temp = temp->next;
}
printf("\n");
}

int main() {
struct Node* head = NULL;

// Example: Insert nodes


insertAtPosition(&head, 10, 1);
insertAtPosition(&head, 20, 2);
insertAtPosition(&head, 0, 3);
insertAtPosition(&head, 25, 4);

// Print the final list


printList(head);

return 0;
}
o Inserting at the end.
 Deletion: The deletion operation can be performed in three ways as follows…
o Deleting from the Beginning of the list
o Deleting from the End of the list
o Deleting a Specific Node
 Display: This process displays the elements of a double-linked list.

C program for the all operations in the Doubly Linked List


#include <stdio.h>
#include <stdlib.h>

// Linked List Node


struct node {
int info;
struct node *prev, *next;
};
struct node* start = NULL;

// Function to traverse the linked list


void traverse()
{
// List is empty
if (start == NULL) {
printf("\nList is empty\n");
return;
}
// Else print the Data
struct node* temp;
temp = start;
while (temp != NULL) {
printf("Data = %d\n", temp->info);
temp = temp->next;
}
}

// Function to insert at the front


// of the linked list
void insertAtFront()
{
int data;
struct node* temp;
temp = (struct node*)malloc(sizeof(struct node));
printf("\nEnter number to be inserted: ");
scanf("%d", &data);
temp->info = data;
temp->prev = NULL;

// Pointer of temp will be


// assigned to start
temp->next = start;
start = temp;
}

// Function to insert at the end of


// the linked list
void insertAtEnd()
{
int data;
struct node *temp, *trav;
temp = (struct node*)malloc(sizeof(struct node));
temp->prev = NULL;
temp->next = NULL;
printf("\nEnter number to be inserted: ");
scanf("%d", &data);
temp->info = data;
temp->next = NULL;
trav = start;

// If start is NULL
if (start == NULL) {

start = temp;
}

// Changes Links
else {
while (trav->next != NULL)
trav = trav->next;
temp->prev = trav;
trav->next = temp;
}
}

// Function to insert at any specified


// position in the linked list
void insertAtPosition()
{
int data, pos, i = 1;
struct node *temp, *newnode;
newnode = malloc(sizeof(struct node));
newnode->next = NULL;
newnode->prev = NULL;

// Enter the position and data


printf("\nEnter position : ");
scanf("%d", &pos);

// If start==NULL,
if (start == NULL) {
start = newnode;
newnode->prev = NULL;
newnode->next = NULL;
}

// If position==1,
else if (pos == 1) {
// this is author method its correct but we can simply call insertAtfront()
function for this special case
/* newnode->next = start;
newnode->next->prev = newnode;
newnode->prev = NULL;
start = newnode; */
// now this is improved by Jay Ghughriwala on geeksforgeeks
insertAtFront();
}

// Change links
else {
printf("\nEnter number to be inserted: ");
scanf("%d", &data);
newnode->info = data;
temp = start;
while (i < pos - 1) {
temp = temp->next;
i++;
}
newnode->next = temp->next;
newnode->prev = temp;
temp->next = newnode;
temp->next->prev = newnode;
}
}

// Function to delete from the front


// of the linked list
void deleteFirst()
{
struct node* temp;
if (start == NULL)
printf("\nList is empty\n");
else {
temp = start;
start = start->next;
if (start != NULL)
start->prev = NULL;
free(temp);
}
}

// Function to delete from the end


// of the linked list
void deleteEnd()
{
struct node* temp;
if (start == NULL)
printf("\nList is empty\n");
temp = start;
while (temp->next != NULL)
temp = temp->next;
if (start->next == NULL)
start = NULL;
else {
temp->prev->next = NULL;
free(temp);
}
}

// Function to delete from any specified


// position from the linked list
void deletePosition()
{
int pos, i = 1;
struct node *temp, *position;
temp = start;

// If DLL is empty
if (start == NULL)
printf("\nList is empty\n");

// Otherwise
else {
// Position to be deleted
printf("\nEnter position : ");
scanf("%d", &pos);

// If the position is the first node


if (pos == 1) {
deleteFirst(); // im,proved by Jay Ghughriwala on GeeksforGeeks
if (start != NULL) {
start->prev = NULL;
}
free(position);
return;
}

// Traverse till position


while (i < pos - 1) {
temp = temp->next;
i++;
}
// Change Links
position = temp->next;
if (position->next != NULL)
position->next->prev = temp;
temp->next = position->next;

// Free memory
free(position);
}
}

// Driver Code
int main()
{
int choice;
while (1) {

printf("\n\t1 To see list\n");


printf("\t2 For insertion at"
" starting\n");
printf("\t3 For insertion at"
" end\n");
printf("\t4 For insertion at "
"any position\n");
printf("\t5 For deletion of "
"first element\n");
printf("\t6 For deletion of "
"last element\n");
printf("\t7 For deletion of "
"element at any position\n");
printf("\t8 To exit\n");
printf("\nEnter Choice :\n");
scanf("%d", &choice);

switch (choice) {
case 1:
traverse();
break;
case 2:
insertAtFront();
break;
case 3:
insertAtEnd();
break;
case 4:
insertAtPosition();
break;
case 5:
deleteFirst();
break;
case 6:
deleteEnd();
break;
case 7:
deletePosition();
break;

case 8:
exit(1);
break;
default:
printf("Incorrect Choice. Try Again \n");
continue;
} }
return 0;}

3. Circular Linked List:


A circular linked list is a type of linked list in which the first and the last nodes are
also connected to each other to form a circle, there is no NULL at the end.

Operations on Circular Linked List:


The following operations are performed on a Circular Linked List
 Insertion: The insertion operation can be performed in three ways:

o Insertion at the beginning of the list


#include <stdio.h>
#include <stdlib.h>

// Structure of a linked list node


struct node {
int info;
struct node* next;
};

// Pointer to last node in the list


struct node* last = NULL;

// Function to insert a node in the


// starting of the list
void insertAtFront(int data)
{
// Initialize a new node
struct node* temp;
temp = (struct node*)malloc(sizeof(struct node));

// If the new node is the only


// node in the list
if (last == NULL) {
temp->info = data;
temp->next = temp;
last = temp;
}

// Else last node contains the


// reference of the new node and
// new node contains the reference
// of the previous first node
else {
temp->info = data;
temp->next = last->next;

// last node now has reference


// of the new node temp
last->next = temp;
}
}

// Function to print the list


void viewList()
{
// If list is empty
if (last == NULL)
printf("\nList is empty\n");

// Else print the list


else {
struct node* temp;
temp = last->next;

// While first node is not


// reached again, print,
// since the list is circular
do {
printf("\nData = %d", temp->info);
temp = temp->next;
} while (temp != last->next);
}
}

// Driver Code
int main()
{
// Function Call
insertAtFront(10);
insertAtFront(20);
insertAtFront(30);

// Print list
viewList();

return 0;
}
o Insertion at the end of the list
o Insertion in between the nodes
#include <stdio.h>
#include <stdlib.h>

// Structure of a linked list node


struct node {
int info;
struct node* next;
};

// Pointer to last node in list


struct node* last = NULL;

// Function to add a new node


// at the end of the list
void addatlast()
{
// Stores number to be inserted
int data;

// Initialize a new node


struct node* temp;
temp = (struct node*)malloc(sizeof(struct node));

// Input data
printf("\nEnter data to be inserted : \n");
scanf("%d", &data);

// If the new node is the


// only node in the list
if (last == NULL) {
temp->info = data;
temp->next = temp;
last = temp;
}

// Else the new node will be the


// last node and will contain
// the reference of head node
else {
temp->info = data;
temp->next = last->next;
last->next = temp;
last = temp;
}
}

// Function to insert after any


// specified element
void insertafter()
{
// Stores data and element after
// which new node is to be inserted
int data, value;

// Initialize a new node


struct node *temp, *n;

// Input data
printf("\nEnter number after which"
" you want to enter number: \n");
scanf("%d", &value);
temp = last->next;

do {

// Element after which node is


// to be inserted is found
if (temp->info == value) {
n = (struct node*)malloc(sizeof(struct node));
// Input Data
printf("\nEnter data to be"
" inserted : \n");
scanf("%d", &data);
n->info = data;
n->next = temp->next;
temp->next = n;

// If temp is the last node


// so now n will become the
// last node
if (temp == last)
last = n;
break;
}
else
temp = temp->next;
} while (temp != last->next);
}

// Function to print the list


void viewList()
{
// If list is empty
if (last == NULL)
printf("\nList is empty\n");

// Else print the list


else {
struct node* temp;
temp = last->next;
do {
printf("\nData = %d", temp->info);
temp = temp->next;
} while (temp != last->next);
}
}

// Driver Code
int main()
{
// Initialize the list
addatlast();
addatlast();
addatlast();

// Function Call
insertafter();

// Print list
viewList();
return 0;
}
 Deletion: The deletion operation can be performed in three ways:

o Deleting from the Beginning of the list


#include <stdio.h>
#include <stdlib.h>

// Structure of a linked list node


struct node {
int info;
struct node* next;
};

// Pointer to last node in list


struct node* last = NULL;

// Function to add a new node


// at the end of the list
void addatlast(int data)
{
// Initialize a new node
struct node* temp;
temp = (struct node*)malloc(sizeof(struct node));

// If the new node is the only


// node in the list
if (last == NULL) {
temp->info = data;
temp->next = temp;
last = temp;
}

// Else the new node will be the


// last node and will contain
// the reference of head node
else {
temp->info = data;
temp->next = last->next;
last->next = temp;
last = temp;
}
}

// Function to delete the first


// element of the list
void deletefirst()
{
struct node* temp;

// If list is empty
if (last == NULL)
printf("\nList is empty.\n");

// Else last node now contains


// reference of the second node
// in the list because the
// list is circular
else {
temp = last->next;
last->next = temp->next;
free(temp);
}
}

// Function to print the list


void viewList()
{
// If list is empty
if (last == NULL)
printf("\nList is empty\n");

// Else print the list


else {
struct node* temp;
temp = last->next;
do {
printf("\nData = %d", temp->info);
temp = temp->next;
} while (temp != last->next);
}
}

// Driver Code
int main()
{
// Initialize the list
addatlast(10);
addatlast(20);
addatlast(30);

printf("Before deletion:\n");
viewList();

// Function Call
deletefirst();

// Print list
printf("\n\nAfter deletion:\n");
viewList();

return 0;
}
o Deleting from the End of the list
// C program for the above operation
#include <stdio.h>
#include <stdlib.h>

// Structure of a linked list node


struct node {
int info;
struct node* next;
};

// Pointer to last node in list


struct node* last = NULL;

// Function to add a new node


// at the end of the list
void addatlast(int data)
{
// Initialize a new node
struct node* temp;
temp = (struct node*)malloc(sizeof(struct node));

// If the new node is the only


// node in the list
if (last == NULL) {
temp->info = data;
temp->next = temp;
last = temp;
}

// Else the new node will be


// last node and will contain
// the reference of head node
else {
temp->info = data;
temp->next = last->next;
last->next = temp;
last = temp;
}
}

// Function to delete the last node


// in the list
void deletelast()
{
struct node* temp;

// If list is empty
if (last == NULL)
printf("\nList is empty.\n");

temp = last->next;
// Traverse the list till
// the second last node
while (temp->next != last)
temp = temp->next;

// Second last node now contains


// the reference of the first
// node in the list
temp->next = last->next;
last = temp;
}

// Function to print the list


void viewList()
{
// If list is empty
if (last == NULL)
printf("\nList is empty\n");

// Else print the list


else {
struct node* temp;
temp = last->next;
do {
printf("\nData = %d", temp->info);
temp = temp->next;
} while (temp != last->next);
}
}

// Driver Code
int main()
{
// Initialize the list
addatlast(10);
addatlast(20);
addatlast(30);

printf("Before Deletion:\n");
viewList();

// Function Call
deletelast();

// Print the list


printf("\n\nAfter Deletion:\n");
viewList();

return 0;}

o Deleting a Specific Node


// C program for the above operation
#include <stdio.h>
#include <stdlib.h>

// Structure of a linked list node


struct node {
int info;
struct node* next;
};

// Pointer to last node in list


struct node* last = NULL;

// Function to add a new node


// at the end of the list
void addatlast()
{
// Stores number to be inserted
int data;

// Initialize a new node


struct node* temp;
temp = (struct node*)malloc(sizeof(struct node));

// Input data
printf("\nEnter data to be inserted : \n");
scanf("%d", &data);

// If the new node is the


// only node in the list
if (last == NULL) {
temp->info = data;
temp->next = temp;
last = temp;
}

// Else the new node will be


// last node and will contain
// the reference of head node
else {
temp->info = data;
temp->next = last->next;
last->next = temp;
last = temp;
}}

// Function to delete an element


// at a specified index in the list
void deleteAtIndex()
{
// Stores the index at which
// the element is to be deleted
int pos, i = 1;
struct node *temp, *position;
temp = last->next;

// If list is empty
if (last == NULL)
printf("\nList is empty.\n");

// Else
else {

// Input Data
printf("\nEnter index : ");
scanf("%d", &pos);

// Traverse till the node to


// be deleted is reached
while (i <= pos - 1) {
temp = temp->next;
i++;
}

// After the loop ends, temp


// points at a node just before
// the node to be deleted

// Reassigning links
position = temp->next;
temp->next = position->next;

free(position);
}}

// Function to print the list


void viewList()
{
// If list is empty
if (last == NULL)
printf("\nList is empty\n");

// Else print the list


else {
struct node* temp;
temp = last->next;
do {
printf("\nData = %d", temp->info);
temp = temp->next;
} while (temp != last->next);
}}

// Driver Code
int main()
{
// Initialize the list
addatlast();
addatlast();
addatlast();

// Function Call
deleteAtIndex();

// Print the list


viewList();

return 0;
}
 Display: This process displays the elements of a Circular linked list.

Linked List vs. Array:


Array Linked List

Linked Lists are not stored in


Arrays are stored in contiguous location.
contiguous location.

Fixed size (Dynamic Sized Arrays also


Dynamic Size
internally use fixed sized arrays)

Only store elements no extra reference / It stores both data and address of next
pointer. node.

Elements can be access by traversing


Elements can be accessed easily in O(1)
through all the nodes till we reach the
time.
required node.

Insertion and deletion operation is Insertion and deletion operation is


slower than Linked List. faster than Array.

Time Complexity Analysis of Linked List and Array:

Operation Linked list Array

Random Access O(N) O(1)

Insertion and deletion at beginning O(1) O(N)

Insertion and deletion at end O(N) (If we maintain only head) O(1)

Insertion & deletion at a random position O(N) O(N)


Why is a linked list preferred over an array?
Following are the reason that linked lists are preferred over array
 Nodes in a linked array, insertions, and deletions can be done at any point in the
list at a constant time.
 Arrays are of fixed size and their size is static but Linked lists are dynamic and
flexible and can expand and shrink their size.
 Linked lists provide an efficient way of storing related data and performing basic
operations such as insertion, deletion, and updating of information at the cost of
extra space required for storing the address.
 Insertion and deletion operations in the linked list are faster as compared to the
array.

Advantages of Arrays over Linked List :


 Random Access: We can access ith item in O(1) time (only some basic
arithmetic required using base address). In case of linked lists, it is O(n)
operation due to sequential access.
 Cache Friendliness: Array items (Or item references) are stored at contiguous
locations which makes array cache friendly (Please refer Spatial locality of
reference for more details)
 Easy to use: Arrays are relatively very easy to use and are available as core of
programming languages
 Less Overhead: Unlike linked list, we do not have any extra references /
pointers to be stored with every item.

Advantages of linked list over arrays:


 Dynamic size: Linked lists are dynamic and flexible and can expand and shrink
their size
 Efficient insertion and deletion: We only need to change few pointers (or
references) to insert (or delete) an item in the middle. Insertion and deletion at
any point in a linked list take O(1) time. Whereas in an array data structure,
insertion / deletion in the middle takes O(n) time.
 Implementation of Queue and Dequeue: Simple array implementation is not
efficient at all. We must use circular array to efficiently implement which is
complex. But with linked list, it is easy and straightforward.
 Space Efficient in Some Cases: Linked List might turn out to be more space
efficient compare to arrays in cases where we cannot guess the number of
elements in advance. In case of arrays, the whole memory for items is allocated
together. Even with dynamic sized arrays like vector in C++ or list in Python or
Array List in Java. The internal working involves de-allocation of whole memory
and allocation of a bigger chunk when insertions happen beyond the current
capacity.
 Circular List with Deletion/Addition: Circular Linked Lists are useful to
implement CPU round robin scheduling or similar requirements in the real world
because of the quick deletion/insertion in a circular manner.

Disadvantages of linked list over arrays:


 If the array is sorted we can apply binary search to search any element which
takes O(log(n)) time. But even if the linked list is sorted we cannot apply binary
search and the complexity of searching elements in the linked list is O(n).
 A linked list takes more memory as compared to the array because extra memory
space is required for the pointer with each element in the linked list.
End Unit-1.

You might also like