Unit 1
Unit 1
Unit 2: Stacks: Abstract Data Type, Primitive Stack operations: Push & Pop, Array
and Linked Implementation of Stack in C, Application of stack: Prefix and Postfix
Expressions, Evaluation of postfix expression, Iteration and Recursion- Principles of
recursion, Tail recursion, Removal of recursion Problem solving using iteration and
recursion with examples such as binary search, Fibonacci numbers, and Hanoi towers.
Tradeoffs between iteration and recursion.
Queues: Operations on Queue: Create, Add, Delete, Full and Empty, Circular queues,
Array and linked implementation of queues in C, Dequeue and Priority Queue.
Unit 4: Trees: Basic terminology used with Tree, Binary Trees, Binary Tree
Representation: Array Representation and Pointer(Linked List) Representation,
Binary Search Tree, Strictly Binary Tree ,Complete Binary Tree . A Extended Binary
Trees, Tree Traversal algorithms: Inorder, Preorder and Postorder, Constructing
Binary Tree from given Tree Traversal, Operation of Insertion , Deletion, Searching
& Modification of data in Binary Search. Threaded Binary trees, Traversing
Threaded Binary trees. Huffman coding using Binary Tree. Concept & Basic
Operations for AVL Tree , B Tree & Binary Heaps
Unit 5: Graphs: Terminology used with Graph, Data Structure for Graph
Representations: Adjacency Matrices, Adjacency List, Adjacency. Graph Traversal:
Depth First Search and Breadth First Search, Connected Component, Spanning Trees,
Minimum Cost Spanning Trees: Prims and Kruskal algorithm. Transitive Closure and
Shortest Path algorithm:Warshal Algorithm and Dijikstra Algorithm.
Items like ID, Age, Gender, First, Middle; Last, Street, Locality, etc. are basic data items in
the aforementioned sample. The Name and the Address, on the other hand, are group data
items.
The data structure indicates how data is organized in memory. Data structures are ways to
organize, store, retrieve, and manage data efficiently. It helps in structuring data so that it can
be accessed and modified effectively. In programming, choosing the right data structure can
make a huge difference in the performance and scalability of an application.
1. Correctness: Data structures are engineered to operate flawlessly across various input
scenarios, tailored to the specific challenges they aim to resolve. Essentially, the primary
objective of a data structure is to ensure correctness, which is contingent upon the issues it's
intended to tackle.
2. Efficiency: In addition to correctness, data structures must demonstrate efficiency by
swiftly managing data while minimizing resource consumption, such as memory usage. The
real-time efficiency of a data structure significantly influences the outcome of a process,
determining its success or failure.
Some Key Features of Data Structures:
Some of the Key Features of Data Structures Include:
o Efficient Data Storage: It enables efficient management and retrieval of data,
making operations such as searching, inserting, and deleting faster.
o Data Manipulation: Structures such as stacks, queues, and trees facilitate specific
operations like push/pop, enqueue/dequeue, and hierarchical traversal.
o Data Organization: Organizing data logically enables better data processing and
operations.
o Ease of Access: Data structures such as arrays and linked lists offer quick access to
elements, depending on their design.
o Flexibility: Various structures are appropriate for different scenarios, providing
flexibility in programming.
o Optimization: Selecting the appropriate data structure can greatly enhance the speed
and efficiency of an algorithm.
o Dynamic Size: Some data structures, such as linked lists and dynamic arrays, can
dynamically adjust their size to accommodate varying amounts of data.
1. Data structures aid in sorting out the information stored in a computer's memory.
2. Data structures aid in the representation of information in databases.
3. Data structures enable the use of algorithms to data search (For example, search
engine).
4. We can use data structures to create data manipulation techniques (For example, word
processors).
5. Data structures can be used to create algorithms for data analysis (For example, data
miners).
6. Data structures support the generation of data by algorithms (For example, a random
number generator).
7. Data structures can also support data compression and decompression methods (For
example, a zip utility).
8. Data structures can also be used to develop algorithms for encrypting and decrypting
data (For example, a security system).
9. We can create software that manages files and directories with the aid of data
structures (For example, a file manager).
10. Data structures can also be used to create software that renders graphics. (A web
browser or 3D rendering programme, for instance.)
Any software or programme is constructed from data structures. It can be very difficult for a
programmer to choose the right data structure for a programme.
When discussing data structures, the following basic terms are frequently used:
1. Data: Data can be viewed as a set of values or as a basic value. Examples of
employee-related data include the name and ID of the employee.
2. Data Items: Data Items are single units of value.
3. Group Items: Group Items are data items that have lower-level data items. An
employee's name, for instance, could include their first, middle, and last names.
4. Elementary Items: Elementary Items are data items that cannot be subdivided into
other data items. Using an employee's ID as an illustration.
5. Entity and Attribute: An entity represents a class of specific items. It is made up of
several Qualities. Each attribute represents a particular attribute of that entity. For
instance.
6. Field: Field refers to a single fundamental piece of data that represents an entity's
attribute.
7. Record: A Record is a grouping of various data pieces. The name, ID, address, and
job title of the employee entity, for instance, can be grouped to create the record for
the employee.
8. File: A File is a grouping of distinct Records of the same entity type. For instance, if
there are 100 employees, the corresponding file will have 25 records with information
about each person.
Example:
An Entity Set is made up of entities with related qualities. A range of values, or the set of all
potential values that could be assigned to a given property, exists for each attribute of an
entity set.
When referring to data that has certain characteristics of meaningful or processed data, the
term "information" is occasionally used.
The Linear Data Structures are further divided into two categories based on memory
allocation:
1. Static Data Structures: Static Data Structures are highly defined data
structures. The sizes of these data structures cannot be changed by the
user after they are created because memory is allocated to them at
compile time; however, the content can be updated. An array is a good
example of a static data structure because it has a fixed size and allows
later data modifications.
2. Dynamic Data Structures: Dynamic Data Structures are variable sized data
structures. The memory of these data structures is allocated at runtime,
and their size changes as the code is executed. In addition, during code
execution, the user has the ability to change the size and data blocks
stored in these data structures. Some examples of dynamic data
structures include Linked Lists, Stacks, and Queues.
1. Arrays
An array, the basic data structure, combines multiple pieces of data of the same type into a
unified variable. Instead of dispersing these values across variables, we aggregate them into
one. It is important to note that this does not mean combining all the same values in every
context into a single framework. However, there are generally situations where related
variables sharing the same data are inherently apt to be sorted into a hierarchy.
An array is a collection of objects where each element occupies a specific position. All
elements of an array have the same variable name but different index numbers, also known as
subscripts. With these indices, we can retrieve any data element from the array. A key
characteristic of arrays is that the data is stored in adjacent memory, allowing users to use
their indices through the elements of the array.
Figure 1.3 An Array
2. Linked Lists
Another example of a dynamic linear data structure for holding a collection of data elements
is a linked list. The nodes in this structure represent data objects, which are connected to each
other through links or pointers. Each node has two locations: a text location, the building of
the data, and a pointer location, which contains the addresses of the next nodes in the list. The
last node in a linked list contains a null pointer, indicating its end. Unlike arrays, linked lists
can be resized dynamically, to suit the needs of the user.
Figure1.4. A Linked List
a. Singly Linked List: The most common type of linked list is the singly linked list,
where each node contains data along with a pointer field pointing to the next node.
b. Doubly Linked List: A doubly linked list comprises an information field and two
pointer fields. The data resides in the information field, while one pointer field holds a
reference to the next node, and the other pointer field contains the location of the
previous node. This bidirectional linkage allows traversal in both forward and
backward directions.
c. Circular Linked List: The Singly Linked List and the Circular Linked List share
similarities. However, the distinguishing feature of the Circular Linked List is that its
last node points back to the first node, forming a circular loop.
a. Linked Lists facilitate the implementation of fixed-size stacks, queues, binary trees, and
graphs.
b. Dynamic memory management feature of the operating system can also be utilized.
c. Linked Lists offer a method to implement polynomials for mathematical operations.
d. Circular linked lists are suitable for constructing round-robin task execution in operating
systems or application operations.
e. Circular Linked Lists find application in Slide Shows, especially when users need to return
to the first slide after viewing the last one.
f. Browser's forward and backward navigation buttons are implemented using doubly linked
lists, enabling users to move between opened pages of a website.
3. Stacks
Stacks, a form of linear data structure, adhere to the Last In, First Out (LIFO) principle,
allowing operations like insertion and deletion from the top of the stack. Both contiguous
memory, like an Array, and non-contiguous memory, such as a Linked List, can serve as
implementations for stacks. Stacks manifest in everyday scenarios such as piles of books, a
deck of cards, money, and various other contexts.
The insertion and removal of fresh books from the top of the stack are examples of activities
that are carried out from only one end of a stack, as seen in the above image. It suggests that
just the top of the stack can be used for insertion and deletion in the stack. At any given time,
we are only able to access the Stack's tops.
Push: Push operation refers to the action of adding a new element to the stack.
Pop: Pop Operation refers to an operation that removes or deletes elements from the
stack.
Figure 1.6 A Stack
4. Queues
Like a stack, a queue is a linear data structure but with specific constraints on element
insertion and deletion. Elements are added to one end of the queue and removed from the
opposite end, adhering to the FIFO (First In, First Out) principle. Queues can be implemented
using arrays, linked lists, or even stacks. Examples of queues in daily life include lines at car
washes, escalators, and ticket counters.
Figure 1.7 A Real-life Example of Queue
A real-life example, such as a movie ticket counter, illustrates the queue system where the
first person to arrive is the first to be served, ensuring fairness. Eventually, the last-arriving
customer will also receive service. The queue operates bidirectional, accommodating various
actions. Similarly, a food court line adds customers from the back and serves them from the
front once they've received their requested services.
a. Enqueue: Enqueue refers to the process of adding certain data elements to a queue.
The rear pointer is always used to assist in inserting the element.
b. Dequeue: Dequeue is the phrase for deleting or removing data pieces from the Queue.
The front pointer is always used to assist in deleting the element.
Figure 1.8 A Queue
a. Queues are often used in a special way of searching through graphs called breadth-first
search.
b. In computer systems, queues are used in various tasks like managing jobs in the operating
system. For example, when you press keys on your keyboard or print documents, those
actions are managed using queues.
c. Queues are important in managing different tasks in a computer, such as deciding which
job the CPU should do next or managing data storage.
d. When you're downloading files using a web browser, priority queues help in deciding
which file gets downloaded first.
e. Queues are helpful in transferring data between devices like printers, keyboards, and the
main part of the computer.
f. Dealing with sudden tasks from user applications, like interruptions, is also something
queues help with in a computer system.
Non-Linear Data Structures
In non-linear data structures, the data pieces aren't arranged in a straight line. So, you can't
add or remove data in a straightforward order like you do with linear structures. Instead,
the data are organized in a hierarchical way, where many pieces of data are connected to
each other in a sort of tree-like structure.
Types of Non-Linear Data Structures
1. Trees
Each node in a tree holds a value as well as a list of pointers to other nodes, making trees
non-linear data structures and hierarchies (the "children").
A specialised approach to gathering and organising data in the computer for better use is the
Tree data structure. It has an edge-connected centre node, structural nodes, and sub-nodes.
We might also state that the connected roots, branches, and leaves make up the tree data
structure.
a. Binary Tree: A binary tree is a tree data structure in which each parent node can have
a maximum of two children.
b. Binary Search Tree: We can easily maintain a sorted list of numbers in a Binary
Search Tree, a Tree data structure.
c. AVL Tree: A self-balancing Binary Search Tree called an AVL Tree keeps additional
data at each node known as a Balancing Factor, which can have a value of -1, 0, or
+1.
d. B-Tree: The self-balancing Binary Search Tree variant known as a B-Tree allows
each node to include multiple keys and more than two children.
Some Applications of Trees
a. Hierarchical structures in computer systems, such as directories and file systems, are
implemented using trees.
b. Trees are employed to establish the navigational structure of websites.
c. Trees can be utilized to develop code similar to Huffman's code.
d. Trees find utility in gaming applications for decision-making processes.
e. Trees are instrumental in implementing priority queues for priority-based OS scheduling
functions.
f. Compilers of many computer languages utilize trees to parse expressions and statements.
g. Trees serve as a means of storing data keys for indexing in database management systems
(DBMS).
h. Spanning trees enable decision-routing in computer and communications networks.
i. Path-finding algorithms in video games, robotics, and artificial intelligence (AI)
applications incorporate trees.
2. Graphs
Another illustration of a non-linear data structure is a graph, which consists of a finite number
of nodes or vertices and the edges bridging them. The use of graphs in solving real-world
issues denotes the issue as a network, such as social networks, circuit networks, or telephone
networks. One user in a telephone network may be represented by one of the nodes, or
vertices, of a graph, while the relationship between them could be represented by one of the
edges.
The mathematical structure known as the Graph data structure, G, is made up of a collection
of vertices, or V, and a set of edges, or E, as shown below:
G = (V,E)
The graph shown above is made up of seven vertices (A, B, C, D, E, F, and G) and ten edges
(A, B), (A, C), (B, C), (B, D), (B, E), (C, D), (D, E), (D, F), and (E, F).
Depending upon the position of the vertices and edges, the Graphs can be classified into
different types:
a. Trivial Graph: A graph with only one vertex is referred to as a trivial graph.
b. Null Graph: A graph devoid of any edges is termed a null graph.
c. Simple Graph: A simple graph is characterized by its absence of multiple edges or self-
loops.
d. Multi Graph: A graph featuring multiple edges without any self-loops is classified as a
multi graph.
e. Pseudo Graph: A pseudo graph is distinguished by its abundance of edges and inclusion
of self-loops.
f. Non-Directed Graph: A graph lacking directed edges is designated as a non-directed
graph.
g. Directed Graph: A directed graph consists solely of directed connections linking its
vertices.
h. Connected Graph: A connected graph possesses at least one path connecting every pair
of vertices.
i. Disconnected Graph: A disconnected graph is one in which there is no path connecting at
least one pair of vertices.
j. Regular Graph: A graph where every vertex has an identical degree is termed a regular
graph.
k. Complete Graph: In a complete graph, every vertex is connected to every other vertex by
an edge.
l. Cycle Graph: A graph containing at least three vertices and edges that form a cycle is
known as a cycle graph.
m. Cyclic Graph: A graph is deemed cyclic if it contains at least one cycle.
n. Acyclic Graph: An acyclic graph is one that lacks any cycles.
o. Finite Graph: A graph with a finite number of vertices and edges is referred to as a finite
graph.
p. Infinite Graph: An infinite graph possesses an infinite number of vertices and edges.
q. Bipartite Graph: In a bipartite graph, vertices can be divided into two independent sets, A
and B, with edges only connecting vertices from set A to those in set B.
r. Planar Graph: A graph that can be drawn in a single plane without any crossing edges is
termed a planar graph.
s. Euler Graph: A graph is Eulerian if all its vertices have even degrees.
t. Hamiltonian Graph: A Hamiltonian graph is a connected graph that contains a
Hamiltonian circuit, traversing each vertex exactly once.
Some Applications of Graphs:
a. Graphs are instrumental in representing routes and networks in transportation, travel, and
communication applications.
b. GPS paths are depicted using graph structures.
c. Graphs aid in visualizing relationships within social networks and other network-based
software.
d. Mapping applications utilize graphs for various functionalities.
e. Graphs play a crucial role in representing user preferences in e-commerce applications.
f. Utility networks utilize graphs to identify issues faced by local or municipal businesses.
g. Graphs are employed in managing resource availability and usage within organizations.
h. Graphs are utilized to create document link maps of websites, showcasing the
interconnectedness between pages through hyperlinks.
i. Graphs find application in neural networks and robotic movements.
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: When you're going through a bunch of data, you need to make sure you
look at each piece just once to handle it properly. For example, if you want to print
out the names of everyone in a department, you have to go through each name one by
one, making sure not to miss anyone.
2. Search: Another thing you can do with data structures is called "search." This means
finding one or more pieces of data that meet certain criteria. The data you're looking
for might be there or might not be. For example, you might use the search function to
find the names of employees who have worked for more than five years.
3. Insertion: Adding new data to a group that's already there is called "insertion." For
example, if a company hires a new employee, you use the insertion operation to put
that new employee's information into the company's records.
4. Deletion: When you want to take out a particular piece of data from a list, you use
something called "deletion." For example, if someone leaves a job, you'd use the
deletion method to take their name off the list of employees.
5. Sorting: Depending on what you're doing, sorting means putting things in order from
smallest to biggest or biggest to smallest. For example, if you want to find the best
performers, you might sort everyone's performance from highest to lowest and pick
the top three. Or, if you're making a list of employees, you might use sorting to put
their names in alphabetical order.
6. Merge: When you have two lists of things that are already sorted, you can put them
together into one big sorted list using something called a "merge" operation.
Data Types in C
In C, each variable has a corresponding data type. Each data type has a unique set of
operations that can be done on it and varied memory requirements. The type of data that the
variable can store, such as integer, character, floating point, double, etc., is specified. A
collection of data with fixed values, meaning, and features is referred to as a data type.
Types Description
Primitive Data
Types Integer and floating data types are subcategories of arithmetic types.
Types Description
The data type doesn't have a value or operator, and it doesn't give its
Void Types caller a result. Yet, primitive data types include void.
User Defined It is mostly used to give integral constants names, which facilitates
DataTypes reading and maintaining programmes.
Derived Data Types are the data types that are created by deriving
Derived types from primitive or built-in data types.
Different data types also have different ranges up to which they can store numbers. These
ranges may vary from compiler to compiler. Below is a list of ranges along with the
memory requirement and format specifies on the 32-bit GCC compiler.
Memory Format
Data Type (bytes) Range Specifier
int
-2,147,483,648 to
int 4 2,147,483,647 %d
-2,147,483,648 to
long int 4 2,147,483,647 %ld
unsigned long 0 to
long int 8 18,446,744,073,709,551,615 %llu
float 4 %f
1.2E-38 to 3.4E+38
double 8 %lf
1.7E-308 to 1.7E+308
long double 16
3.4E-4932 to 1.1E+4932 %Lf
Integer Types:
The integer data type in C is used to store the whole numbers without decimal values.
Octal values, hexadecimal values, and decimal values can be stored in int data type in C.
We can determine the size of the int data type by using the size of operator in C. Unsigned
int data type in C is used to store the data values from zero to positive numbers but it
can’t store negative values like signed int. Unsigned int is larger in size than signed int and
it uses “%u” as a format specifier in C programming language. Below is the programming
implementation of the int data type in C.
intmain()
{
// Integer value with positive data.
inta = 9;
return0;
}
Output
With positive data, an integer value of 9
With negative data, an integer value -9
Unsigned int value for an integer: 89
Long int value for an integer: 99998
Character Types:
Only one character may be stored in a character data type variable. The character has a
storage size of 1. It is C's most fundamental data type. In practically all compilers, it
stores a single character and uses a single byte of memory.
Range: (-128 to 127) or (0 to 255)
Size: 1 byte
Format Specifier: %c
int main()
{
chara = 'a';
charc;
a++;
printf("Value of a after increment is: %c\n", a);
return0;
}
Output:
A's value is: a
C's value is c.
Floating-Point Types:
The float data type is used to hold floating-point values in C programming. Decimal and
exponential numbers are stored in C using the float keyword. It is used to hold decimal
integers with single precision (numbers with floating point values).
Range: 1.2E-38 to 3.4E+38
Size: 4 bytes
Format Specifier: %f
intmain()
{
floata = 9.0f;
floatb = 2.5f;
// 2x10^-4
floatc = 2E-4f;
printf("%f\n",a);
printf("%f\n",b);
printf("%f",c);
return0;
Output:
9.000000
2.500000
0.000200
Double Types:
In C, double precision decimal numbers (numbers having floating point values) are
stored using the Double data type. It is used to define numerical values that store C
decimal values for numbers. The double data type is essentially a precision data type
that can store 64 bits of floating point or decimal numbers. As double has greater
precision as float, it is more clear that it uses up twice as much memory as the floating-
point type. It can easily handle 16 to 17 numbers either before or after the decimal
point.
Range: 1.7E-308 to 1.7E+308
Size: 8 bytes
Format Specifier: %lf
// C Program to demonstrate
// use of double data type
#include <stdio.h>
intmain()
{
doublea = 123123123.00;
doubleb = 12.293123;
doublec = 2312312312.123123;
printf("%lf\n", a);
printf("%lf\n", b);
printf("%lf", c);
return0;
}
Output:
123123123.000000
12.293123
2312312312.123123
Void Data types:
In C, the void data type is used to indicate the absence of a value. It does not give its caller
a result value. Both values and operations are absent. It is employed to stand in for nothing.
Function return types, function arguments, and pointers to void are all examples of how
void is utilised.
Syntax:
// function return type void
int print(void);
// C program to demonstrate
// use of void pointers
#include <stdio.h>
intmain()
{
intval = 30;
void*ptr = &val;
printf("%d", *(int*)ptr);
return0;
}
Output:
30
Using dynamic memory allocation in C malloc(), calloc(), free() and
realloc():
C has a set of predetermined programming rules because it is a structured language. One of
them involves altering an array's size. A group of items kept in consecutive memory
regions is known as an array.
As can be observed, the above-made array has a length (size) of 9. However, what if this
duration needs to be changed? (size). As an illustration, suppose that only five elements
need to be entered into this array. In this instance, the remaining 4 indices in this array are
essentially a memory leak. So, it is necessary to reduce the array's length (size) from 9 to 5.
In C, this process is known as dynamic memory allocation.
As a result, C Dynamic Memory Allocation can be characterised as a process that enables
the runtime modification of a data structure's size, such as an array.
Several functions are available in C to carry out these tasks. To help with dynamic memory
allocation in C programming, C provides 4 library functions that are defined under the
stdlib.h> header file. As follows:
1. malloc()
2. calloc()
3. free()
4. realloc()
malloc () method
In C, a single big block of memory with the requested size is dynamically allocated using
the "malloc" or "memory allocation" method. It returns a void pointer that can be cast into
any other pointer type. As it doesn't initialise memory during execution, each block has
been originally initialised with the default garbage value.
Syntax:
malloc ptr = (cast-type*) (byte-size)
For instance:
calloc() method :
Syntax:
ptr = realloc(ptr, newSize);
where ptr is reallocated with new size 'newSize'.
Parameters
Back Value
A pointer to the block of memory is returned after a successful allocation using the
malloc() and calloc() functions; otherwise, NULL is returned, signifying a failure.
Algorithm
What is an Algorithm?
"A collection of finite rules or instructions to be followed in calculations or other problem-
solving procedures" is what the word algorithm signifies. Alternatively, "A finite-step
process for solving a mathematical problem that frequently uses recursive operations."
By using the process of making a novel recipe as an example, it can be understood. When
following a new recipe, one must read the directions and carry out each step in the correct
order. The new meal is cooked to perfection as a result of this process. You employ
algorithms every time you use a phone, computer, laptop, or calculator. Similar to this,
algorithms assist programmers in carrying out tasks to produce desired results.
The developed algorithm is language-independent, meaning that it consists only of simple
instructions that can be used to build it in any language and still produce the desired results.
Characteristics of an Algorithm
As opposed to using the normal recipe's printed directions, one would not follow them.
In a similar vein, not all computer instructions written down are algorithms. Certain
instructions must meet the following requirements in order to qualify as an algorithm:
Clear and Unambiguous: The algorithm must be unambiguous and clear. Each of its
steps must have a distinct meaning and be transparent from beginning to end.
Well-Defined Inputs: When an algorithm requests inputs, such inputs must be specific.
It might or might not accept input.
Well-Defined Outputs: The output that will be produced by the algorithm must be
specified in detail and be well-defined. It must produce at least one output.
Finite-ness: The algorithm must be finite, or end after a finite amount of time.
Feasible: For use with the available resources, the algorithm needs to be simple,
generic, and implementable. Modern technology or anything else cannot be used in it.
Language Independent: In order to be implemented with the available resources, the
algorithm needs to be simple, general, and practicable. It cannot use any advanced
technology or anything else.
Properties of Algorithm
Types of Algorithms
There are numerous varieties of algorithms. Many significant algorithms include:
1. Brute Force Algorithm: It is the simplest solution to the issue. When we see an issue,
the first method that comes to mind is a brute force algorithm.
2. Recursive Algorithm: Recursion is the foundation of a recursive algorithm. In this
instance, a problem is divided into multiple smaller components and repeatedly called by
the same function.
3. Backtracking Algorithm: In essence, the backtracking algorithm constructs the solution
by scouring all potential solutions. Using this approach, we continue to develop the answer
in accordance with the criteria. Every time a solution fails, we go back to the original
problem, build on the new one, and repeat the process until the problem is solved or all
potential solutions have been considered.
4. Searching Algorithm: The algorithms used for searching individual components or
groups of elements from a specific data structure are called searching algorithms.
Depending on how they go about things or whatever data structure the element needs to be
in, they can be of several forms.
5. Sorting Algorithm: Sorting is the process of organising a group of facts in a specific
way in accordance with the needs. Sorting algorithms are the ones that assist in carrying out
this duty. Data groupings are often sorted using sorting algorithms in an increasing or
decreasing order.
6. Hashing Algorithm: Similar to how a search algorithm operates, hashing algorithms do
the same. Yet, they have an index with a key ID. In hashing, a key is given to a particular
piece of data.
7. Divide and Conquer Algorithm: This algorithm divides an issue into smaller problems,
resolves each smaller problem in turn, then combines the results to provide the overall
answer. The procedure entails the following three steps:
Divide
Solve
Combine
8. Greedy Algorithm: This kind of algorithm builds the solution piece by piece. The
immediate benefit of the following section serves as the foundation for the solution of that
section. The answer for the following section will be the one offering the greatest benefit.
9. Dynamic Programming Algorithm: In order to avoid repeatedly computing the same
portion of the problem, this algorithm makes use of the idea of using the previously
discovered answer. It separates the issue into more manageable overlapping subproblems
and resolves each one.
10. Randomized Algorithm: We employ a random integer in the randomised process so
that it provides quick benefit. The expected result is determined in part by the random
number.
Advantages of Algorithms:
It is simple to understand.
An algorithm is a step-by-step depiction of a solution to a given problem.
Because the problem is divided into smaller bits or steps in an algorithm
It is simpler for the programmer to turn it into a real programme.
Disadvantages of Algorithms:
Writing an algorithm can be a time-consuming process. Since writing an algorithm
takes a long time, it is indeed time-consuming.
Understanding complex logic with the help of algorithms isn't easy.
Displaying looping and branching statements in an algorithm is challenging.
1. The problem that this algorithm is supposed to answer, or a clearly stated problem.
2. Before solving the problem, the constraints of the problem must be taken into account.
3. The information needed to address the issue.
4. The results that can be anticipated after the issue is resolved.
5. This problem can be solved within the limitations that have been established.
The algorithm is then created using the aforementioned inputs in such a way that it resolves
the issue.
Example: Think about the example that prints the result of adding three numbers.
#include <stdio.h>
int main()
{
return0;
}
Output
Enter the 1st number: 0
Enter the 2nd number: 0
Enter the 3rd number: -1577141152
Issues of Algorithms:
The following are the issues that come up while designing an algorithm:
o How to design algorithms: As we know, an algorithm is a step-by-step procedure,
so we must follow some steps to design an algorithm.
o How to analyse algorithm efficiency: Analysing algorithm efficiency is essential
for understanding its performance, especially as the input size grows.
For a standard algorithm to work, it must be effective. As a result, it's important to keep an
eye on an algorithm's performance. There could be two stages:
1. Priori Analysis: Latin's priori means "before." As a result, Priori analysis describes the
process of validating an algorithm before applying it. This is checked when an
algorithm is written as a series of theoretical steps. The effectiveness of an algorithm is
assessed on the presumption that all other factors, such as processor speed, remain
constant and have no bearing on the implementation. This is often done by the
algorithm's creator. Our investigation is unaffected by the type of hardware or compiler
language. Due to the complexity of the programme, it offers rudimentary solutions.
Based on how much space and time it uses, an algorithm is classified as complicated. As a
result, an algorithm's complexity is determined by how much time and space it will require
to store all the data and execute the algorithm to produce the desired result (input,
temporary data and output). Thus, these two elements determine how effective an
algorithm.
Fixed Part: This is the area that the algorithm without a doubt requires. The size of the
programme, the output variables, and the input variables, for example.
Variable Part: Here is the area where the algorithm's results could differ. For instance,
recursion stack space, temporary variables, and dynamic memory allocation.
As a result, any algorithm's space complexity S(P) S(P) = C + SP(I), where C is the
algorithm's constant component and S(I) is its variable component that depends on the
instance characteristic I
Time Complexity: The time complexity of an algorithm is the amount of time required
for it to run and deliver a result. This might apply to normal operations, conditional if-else
statements, loop statements, etc.
Constant time part: Any instruction that is only ever performed once is included in
this section. A few examples are input, output, if-else, switches, arithmetic operations, etc.
Variable Time Part: This section contains all instructions that are executed n times or
more. using instances like loops, recursion, etc.
• As a result, the time complexity of any algorithm P can be calculated using the formula
T(P) = C + TP(I), where C denotes the method's constant time component and TP(I) is the
algorithm's variable time component that is dependent on the instance characteristic I.
Example: The linear search algorithm's temporal complexity is calculated using the
formula below:
Initially, Constant Time
Second Variable Time in Step 2 (Taking n inputs)
Third step: -Variable time (Till the element's index or the array's length (n) are found)
The final step is constant time.
Constant time is the fifth step.
Constant time is step six. This means that T(P) = 5 + n, or T (n)
Average Case: The average case analysis is not easy to do in most practical cases and it
is rarely done. In the average case analysis, we need to consider every input, its
frequency and time taken by it which may not be possible in many scenarios
Best Case: The Best Case analysis is considered bogus. Guaranteeing a lower bound on
an algorithm doesn't provide any information as in the worst case, an algorithm may
take years to run.
Worst Case: This is easier than average case and gives an upper bound which is useful
information to analyze software products.
Asymptotic Notation
The main idea of asymptotic analysis is to have a measure of the efficiency of algorithms that
don't depend on machine-specific constants and don't require algorithms to be implemented
and time taken by programs to be compared. Asymptotic notations are mathematical tools to
represent the time complexity of algorithms for asymptotic analysis.
Asymptotic Notations:
These notations provide a concise way to express the behavior of an algorithm's time
or space complexity as the input size approaches infinity.
By using asymptotic notations, such as Big O, Big Omega, and Big Theta, we can
categorize algorithms based on their worst-case, best-case, or average-case time or
space complexities, providing valuable insights into their efficiency.
Let the function from the set of natural numbers to itself be represented by g and f. If there
are constants c1, c2 > 0 and a natural number n0 such that c1* g(n) f(n) c2* g(n) for all n
n0, then the function f is said to be (g).
Theta notation
The following expression can be translated as follows: for large values of n (n n0), f(n)
always has a value between c1 * g(n) and c2 * g(n), if f(n) is theta of g(n). For n values
bigger than n0, f(n) must also not be negative according to the definition of theta.
Eliminating low-order terms and ignoring leading constants is a straightforward method for
converting an expression to its Theta notation. For instance, Consider the phrase 3n3 + 6n2
+ 6000 = (n3); eliminating the lower order components is always acceptable because,
regardless of the constants used, there will always be a number(n) after which (n3) has
greater values than (n2). We write g(n) is the following collection of functions for a given
function.
If f(n) describes the running time of an algorithm, f(n) is O(g(n)) if there exist a positive
constant C and n0 such that, 0 ≤ f(n) ≤ cg(n) for all n ≥ n0. It returns the highest possible
output value (big-O) for a given input.
For example, Consider the case of Insertion Sort. It takes linear time in the best case and
quadratic time in the worst case. We can safely say that the time complexity of the Insertion
sort is O(n2).
The Big-O notation is useful when we only have an upper bound on the time complexity of
an algorithm. Many times we easily find an upper bound by simply looking at the
algorithm.
3. Omega Notation (Ω-Notation):
The lowest bound of an algorithm's running time is shown in omega notation. As a
result, it offers an algorithm's best case complexity.
Let the function from the set of natural numbers to itself be represented by g and f. If
there is a constant c > 0 and a natural number n0 such that c*g(n) f(n) for all n n0, then
the function f is said to be (g).
Mathematical Representation of Omega notation:
Let's use the same example of an insertion sort again. Insertion Sort's time complexity can
be expressed as (n), although this is not very helpful information since we are typically
interested in the worst-case scenario and occasionally the average scenario.
Properties of Asymptotic Notations
1. General Properties
Example:
2. Transitive Properties
Example:
We can say,
3. Reflexive Properties
If p(n) is given, then p(n) is O(p(n)). Since the maximum value of p(n) will be p(n) itself!
Example:
4. Symmetric Properties
Example:
Example:
If (n) = n, f(n) = n²
The optimal algorithm is one that helps with problem solving while using less memory and
producing results more quickly. Yet, in general, it is not always possible to satisfy both of
these conditions at once. A lookup table-based method is the most common situation. This
suggests that it may be possible to save the answers to some queries for each potential
value. The entire lookup table can be written down to solve this problem, but doing so will
take up a lot of space and make it challenging to find the answers quickly. Another
approach is to calculate the findings without documenting anything; this method uses very
little paper but could take some time.So, the less space-efficient your algorithms are, the
more time-efficient they are.
Compressed or Uncompressed data: The issue of data storage can be approached through
a space-time trade-off. Uncompressed data takes up more space but loads faster. Yet,
compressed data requires less storage space but more processing time to conduct the
decompression method. Working directly with compressed data is possible in a variety of
situations. It is quicker to work with compression than without compression in that scenario
of compressed bitmap indices.
Re-Rendering or Stored images: In this situation, saving just the source and rendering it
as an image would take up more memory but take less time; similarly, caching an image is
quicker than re-rendering but uses up more memory.
Smaller code or Loop Unrolling : Smaller code takes up less memory, but it takes more
time to compute because it has to go back to the beginning of the loop after each iteration.
Loop unrolling can increase binary size but improve execution performance. Although it
takes up more memory space, it takes less time to process.
Lookup tables or RecalculationA lookup table's implementation option allows for full table
inclusion, which shortens computation time but requires more memory. It can recalculate,
or compute table entries, as necessary, lengthening computation times while shortening
memory needs.
For instance: The recurrence relation, in mathematics, defines the Fibonacci Numbers'
sequence Fn:
F n = F n – 1 + F n – 2,
where, F0 = 0 and F1 = 1.
A simple solution to find the Nth Fibonacci term using recursion from the above recurrence
relation.
TimeComplexity: O(2N)
Auxiliary Space: O(1)
An object's behaviour can be described by a set of values and a set of actions, and this
behaviour is known as an abstract data type (ADT). The definition of ADT merely specifies
the actions that must be taken, not how they must be carried out. It is unclear what
algorithms will be utilised to carry out the operations and how the data will be structured in
memory. Because it provides an implementation-independent view, it is dubbed "abstract."
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 that 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. So, a user just needs to be aware of what a data type is
capable of, not how it will be used. Consider ADT as a "black box" that conceals the
internal organisation and design of the data type. 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. Some examples of ADTs are List, Stack, and
Queue.
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, the 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 the data
structures that we are using in a program.
Common ADTs:
Now, let's understand three common ADTs: List, Stack, and Queue ADTs.
1. List ADT
The List ADT (Abstract Data Type) is a collection of elements in a sequential manner
that provides a set of operations without giving details of the internal
implementation. It provides an ordered way to store, access, and modify data.
Operations of List
The List ADT has to store the data sequentially and must have the following operations :
o insert(): For inserting an element at any position in the given list.
o get(): For returning an element at any given position in the list.
o remove(): For removing any element that occurs first from the non-empty list.
o removeAt():For removing the element from a non-empty list at the specified
location.
o replace():For replacing an element with another element at any position.
o size(): For returning the number of elements in the list.
o isEmpty():For returning a true value if the list is empty; otherwise, return a false
value.
o isFull(): For returning a true value if the list is full; otherwise, return a false value.
Only applied to the fixed-size implementations (for example, array-based lists).
2. Stack ADT
The stack ADT follows the LIFO (Last In, First Out) principle. It is a linear data structure.
In a stack ADT, the addition and removal of elements is done from only one end, which is
the top of the stack.
The order of deletion and insertion should be as per the LIFO or FILO Principle in the stack
ADT. It must also support the following operations:
o push(): For inserting an element at one end of the stack, called the top.
o pop(): For removing and returning the element at the top of the stack, provided the
stack is not empty.
o peek(): For returning the element at the top of the stack without removing it,
provided the stack is not empty.
o size(): For returning the total number of elements present in the stack.
o isEmpty(): For returning a true value if the stack is empty; otherwise, return a false
value.
o isFull(): For returning a true value if the stack is full; otherwise, return a false value.
It is only relevant for the fixed-capacity stacks (e.g., array-based).
3. Queue ADT
A linear data structure that follows the FIFO (First In, First Out) principle is the queue
ADT. It allows elements to be removed at the other end(front) and inserted at one end
(rear).
The design of the queue ADT is similar to the stack ADT. It should give support to the
following operations:
o enqueue(): For inserting an element at the end of the queue.
o dequeue(): For removing and returning the first element of the queue, provided the
queue is not empty.
o peek(): For returning the element of the queue without removing it, provided the
queue is not empty.
o size(): For returning the total number of elements present in the queue.
o isEmpty(): For returning a true value if the queue is empty; otherwise, return a false
value.
Features of ADTs
Advantages of ADTs
o Encapsulation: ADTs facilitate a way for encapsulating data and operations into a
single unit. Hence, making it easier to modify and manage the data structure.
o Abstraction: ADTs allow users to use data structures without knowing the
implementation details, which can do simplification programming and reduction in
errors.
o Data Structure Independence: Implementation of ADTs can be done using
different data structures. Thus, making it easier to adapt to the changing
requirements and needs.
o Information Hiding: ADTs can protect the data integrity by taking control of
access and stopping unauthorized modifications.
o Modularity: One ADT can be combined with the other ADT to generate more
complex data structures, which can increase modularity and flexibility in
programming.
Disadvantages of ADTs
o Overhead: Implementation of ADTs can put some overhead in terms of processing
and memory, which can affect performance.
o Complexity: Implementation of ADTs can be complex, especially for complex and
large data structures.
o Learning Curve: To use ADTs, there is a requirement for knowledge of their
implementation and usage, which can take time and effort to learn.
o Limited Flexibility: There can be limitations in the functionality of some ADTs, or
they may not be apt for all types of data structures.
o Cost: Implementation of ADTs may require additional investment and resources,
which can increase the development cost.
Array Data Structure
A group of items kept in consecutive memory regions is known as an array. The goal is to
group objects of the same category for storage. As a result, it is simpler to determine each
element's position by simply adding an offset to a base value, or the address in memory where
the array's first element is stored (generally denoted by the name of the array).
A group of related data elements stored at adjacent memory locations is referred to as an array.
One of the most basic data structures, it allows for random access to each data element by its
index number.
Basic Terminologies
Array Element: Items of an array are known as its elements, which are kept in an
array and can be accessed randomly with the help of their index number.
Array Index: In an array, elements are recognized by their indices. Array index
begins from 0. In other words, zero-based indexing occurs in an array.
Array Length: The number of elements in an array determines the array length.
Contiguous Memory: Array elements are stored back-to-back in memory for fast
access
Representation of an array:
Several programming languages allow us to express an array in different ways. Let's look at an
array declaration in the C language as an example.
As seen in the example above, several of the following points are crucial:
Index begins at 0.
Because the array is 10 items long, 10 elements can be stored in it.
The array's index can be used to access any element.
If you imagine yourself at the bottom of the stairwell in the above image, you can see the
staircase from above. The array's index can be used to uniquely identify each element (in a
similar way as you could identify your friends by the step on which they were on in the above
example).
Why are arrays required?
In computer programming; most cases require storing a large number of data of a similar type.
To store such an amount of data, we need to define a large number of variables. It would be
very difficult to remember the names of all the variables while writing the programs. Instead of
naming all the variables with a different name, it is better to define an array and store all the
elements into it.
Arrays mainly have advantages like random access and cache friendliness over other data
structures that make them useful.
Storing and accessing data: Arrays store elements in a specific order and allow
constant-time O(1) access to any element.
Searching: If data in array is sorted, we can search an item in O(log n) time. We can
also find floor(), ceiling(), kth smallest, kth largest, etc efficiently.
Matrices: Two-dimensional arrays are used for matrices in computations like graph
algorithms and image processing.
Implementing other data structures: Arrays are used as the underlying data
structure for implementing stacks and queues.
Data Buffers: Arrays serve as data buffers and queues, temporarily storing incoming
data like network packets, file streams, and database results before processing.
Efficient and Fast Access: Arrays allow direct and efficient access to any element in
the collection with constant access time, as the data is stored in contiguous memory
locations.
Versatility: Arrays can be used to store a wide range of data types, including integers,
floating-point numbers, characters, and even complex data structures such as objects
and pointers.
Compatibility with hardware: The array data structure is compatible with most
hardware architectures, making it a versatile tool for programming in a wide range of
environments.
Fixed Size: Arrays have a fixed size set at creation. Expanding an array requires
creating a new one and copying elements, which is time-consuming and memory-
intensive.
Memory Allocation Issues: Allocating large arrays can cause memory exhaustion,
leading to crashes, especially on systems with limited resources.
Limited Data Type Support: Arrays support only elements of the same type,
limiting their use with complex data types.
Lack of Flexibility: Fixed size and limited type support make arrays less adaptable
than structures like linked lists or trees.
Basic operations:
An array's data elements are kept together in contiguous regions in the main memory. The
address of the first element in main memory, also known as the base address, is represented by
the array name. The array's elements are each represented by an appropriate index.
1. No indexing: Arr[0] will be the first element of the array (zero-based indexing).
3. n (n-based indexing): The array's first member can be located at any arbitrary index
number.
We've displayed the memory allocation for a 5-by-5 array in the image above. The array has a
0-based method of indexing. The array's base address is 100 bytes. That is arr[0] address's, As
the data type used in this case is 4 bytes in size, each element will occupy 4 bytes in the RAM.
For Example, If an array has 5 elements of type int, then total memory occupied by an array
will be 10 bytes because int has 2 bytes.
The formula is used to calculate the number of elements in an array is given below:
Where:
Array is a data structure that is used to store variables that are of similar data types
at contiguous locations. The main advantage of the array is random access and cache
friendliness. There are mainly three types of the array:
One-dimensional arrays (1D)
2-dimensional arrays (2D),
multidimensional arrays
An array can be declared with the bracket punctuators [ ], as shown in the below syntax:
Data_Type Array_Name[Number_Of_Elements]
The below example shows a declaration of an array having the capacity to hold 50
elements of type integers and the name of that array is arr :
int arr[50];
We can initialize an array in C by assigning value to each index one by one or by using a
single statement as follows −
Example 1 :
arr[0]=12;
arr[1]= 43;
arr[2] = 14;
.
.
.
.
.
arr[49] = 54;
Example 2:
#include <stdio.h>
int main() {
int n;
scanf("%d", &n);
printf("You entered:\n");
for(int i = 0; i < n; i++) {
return 0;
2D array representation:
We can say a Multidimensional array is an array of arrays. Two Dimensional arrays is also
a multidimensional array. In a Multi-Dimensional array, elements of an array are arranged
in the form of rows and columns.
Multidimensional array stores elements in tabular form which is also known as in row-
major order or column-major order.
data_type array_name[size1][size2]….[sizeN];
data_type: It defines the type of data that can be held by an array. Here data_type
can be int, char, float. etc.
array_name: Name of the array
size1, size2,… ,sizeN: Sizes of the dimensions
Two-dimensional array
int arr1[10][20];
Program in C to implement two dimensional array:
#include <stdio.h>
int main() {
int arr[3][3];
int i, j;
// Input elements
scanf("%d", &arr[i][j]);
// Output elements
printf("Matrix is:\n");
printf("%d\t", arr[i][j]);
printf("\n");
}
return 0;
Three-dimensional array:
Declaration of 3D Array:
data_type array_name[size1][size2][size3];
Total elements = 2 × 3 × 4 = 24
Initialization:
int arr[2][2][3] = { { {1, 2, 3}, {4, 5, 6} }, { {7, 8, 9}, {10, 11, 12} }};
C Program for 3D Array:
#include <stdio.h>
int main() {
int arr[2][2][3];
int i, j, k;
// Input elements
scanf("%d", &arr[i][j][k]);
// Output elements
printf("\n");
printf("\n");
return 0;
Difference Table:
Address calculation for 1-D, 2-D, and 3-D elements using row-major and column-major
order:
In data structures, array addressing refers to the process of calculating the memory address of
an element in an array. Arrays are stored in contiguous memory locations, and each element
can be accessed using its index. The formula for calculating the address depends on the type of
array (1D, 2D, or 3D) and the storage order (row-major or column-major).
Where:
Example 1: Get the address of A[1700] given that the base address of an array
A[1300........... 1900] is 1020 and that each element has a size of 2 bytes in the memory.
Solution:
Given:
B = Base Address 1020
Lower Bound of array is 1300.
One element's storage space in any array W = 2 Byte
Index of element with address i = 1700.
Formula used:
Address of A[i] = B + W * (i – LB)
Address of A[1700] = 1020 + 2 * (1700 – 1300)
= 1020 + 2 * (400)
= 1020 + 800
Address of A[1700] = 1820
Example 2: Get the address of A[3] given that the base address of an array A[10] is 1000
and that each element has a size of 4 bytes in the memory.
Solution: Given:
B = Base Address 1000
Lower Bound of array is 0.
One element's storage space in any array W = 4 Byte
Index of element with address i = 3.
Formula used:
An array of arrays can be used to define a 2-dimensional array. The 2-Dimensional arrays
are arranged as matrices, which may be written as array[M][N], where M denotes the
number of rows and N the number of columns.
There are two approaches to determine the address of any element in a 2-Dimensional
array:
Row major ordering places things in successive memory locations as they move across the
rows and then down the following row. In Row major method elements of an array are
arranged sequentially row by row. Thus, elements of first row occupies first set of memory
locations reserved for the array, elements of second row occupies the next set of memory and
so on.
The following formula is used to determine the address of the element using row-major order:
Where:
Example 1: Given an array, arr[1.........10][1.........15] with a base value of 100 and 1 byte
for each element in memory. Using row-major order, locate the address of arr[8][6].
Solution:
Given:
Address at base B = 100
One element's storage space in any array W = 1 byte
row index i= 8
column index j = 6
Lower bound of rows L1 = 1
Total number of column in given matrix N is equal to Upper Bound - Lower Bound + 1.
= 15 – 1 + 1
= 15
Formula:
Address of A[i][j] = B + W*[ N * (i-L 1) + (j-L2)]
The following formula is used to determine the address of the element using row-major order:
Where:
row index i= 8
column index j = 6
Lower bound of rows L1 = 1
Total number of row in given matrix N is equal to Upper Bound - Lower Bound + 1.
= 10 – 1 + 1
= 10
Formula:
Address of A[i][j] = B + W*[ M * (j-L 2) + (i-L1)]
There are two approaches to determine the address of any element in 3-Dimensional arrays:
-
Row Major Order
Column Major Order
Row-Major Order:
Solution:
Given:
Formula used:
= 400 + 2 * (6*6*4)+(6*3)+3
= 400 + 2 * (165)
= 730
Column-Major Order:
Where:
Example: Given an array arr[1:8, -5:5, -10:5] with a base value of 400 and the size of
each element is 4 Bytes in memory find the address of element arr[3][3][3] with the help
of column-major order?
Solution:
Given:
Row Subset of an element whose address to be found i = 3
Column Subset of an element whose address to be found j = 3
Block Subset of an element whose address to be found k = 3
Base address B = 400
Storage size of one element store in any array(in Byte) W = 4
Lower Limit of blocks in matrix L 1 = 1
Lower Limit of row/start row index of matrix L 2 = -5
Lower Limit of column/start column index of matrix L 3 = -10
P (width)= Upper Bound - Lower Bound + 1 = 8 - 1 + 1 = 8
M (row)= Upper Bound - Lower Bound + 1 = 5 - (-5 ) + 1 = 11
Formula used:
Address of A[i][j][k] = B + W *[(P* M * (k-L3) + P*(j-L2) + (i-L1)]
Example:
00304
00570
00000
02600
As most zeroes in a sparse matrix are useless, representing it using a 2D array wastes a
significant amount of memory. Hence, we solely store non-zero elements and don't keep
zeroes along with them. This entails using triples to store non-zero elements (Row,
Column, value).
There are numerous ways to express sparse matrices. Here are two popular representations:
1. Array representation
2. Linked list representation
Implementation:
Output
001133
242312
345726
Time Complexity: O(NM), where N and M are the number of rows and columns in the
sparse matrix, respectively.
Auxiliary Space: O(NM), where N and M are the number of rows and columns,
respectively, in the sparse matrix.
Linked List Data Structure
A linear data structure called a linked list has elements that are not kept in consecutive locations
in memory. As seen in the graphic below, pointers are used to connect the elements in a linked
list:
A linked list is a group of randomly stored nodes, which are objects, in the memory.
Each node has two fields: the data that is stored there and a pointer that points to the
node after it in the memory.
A pointer to the null is contained in the list's final node.
A linked list is made up of nodes, each of which has a data field and a reference (or link) to the
node after it in the list.
The group of elements that are to be saved independently in memory up until this point have
been organised using an array data structure. But, in order to choose the data structure that
will be used throughout the programme, it is important to be aware of the advantages and
disadvantages of Array.
1. The size of array must be known in advance before utilising it in the programme.
2. Raising the array's size is a laborious operation. At runtime, increasing the array's size is
virtually difficult.
3. The RAM must be used to continuously store every element of the array. Any element that
is added to the array requires the relocation of all of its predecessors.
The data structure that can overcome all of an array's drawbacks is a linked list. The usage of
linked lists is advantageous because
1. It uses dynamic memory allocation. The linked list's nodes are all kept in memory in a non-
contiguous fashion and connected via pointers.
2. Sizing is no longer a concern as we do not need to declare its size at the time of
declaration. List expansion is governed by software requirements and memory space
constraints.
Array: A faster way to access an element at a certain index is made possible by the fact that
elements in arrays are kept in contiguous memory locations, resulting in easily calculable
addresses for the elements stored.
Linked List: Because the storage structure of linked lists is less strict and the components
are typically not stored in close proximity to one another, it is necessary to store the elements
with additional tags that refer to the subsequent elements.
Whether data structure would be better appropriate for a particular case depends on the
difference in the data storage technique.
Differences between array and linked-list
Size: Since data can only be stored in contiguous blocks of memory in an array, its size
cannot be altered at runtime due to the risk of overwriting other data.
However, each node in a linked list points to the following one. such that data can exist
at scattered (non-contiguous) addresses; this allows for a dynamic size that can change
at runtime.
Memory allocation: For arrays at compile time and at runtime for linked lists. but, a
dynamically allocated array also allocates memory at runtime.
Memory efficiency: For the same number of elements, linked lists use more memory as
a reference to the next node is also stored along with the data. However, size flexibility
in linked lists may make them use less memory overall; this is useful when there is
uncertainty about size or there are large variations in the size of data elements;
Memory equivalent to the upper limit on the size has to be allocated (even if not all of it
is being used) while using arrays, whereas linked lists can increase their sizes step-by-
step proportionately to the amount of data.
Execution time: Any element in an array can be directly accessed with its index.
However, in the case of a linked list, all the previous elements must be traversed to
reach any element.
Also, better cache locality in arrays (due to contiguous memory allocation) can
significantly improve performance. As a result, some operations (such as modifying a
certain element) are faster in arrays, while others (such as inserting/deleting an element
in the data) are faster in linked lists.
Insertion: In an array, insertion operation takes more time but in a linked list these
operations are fast. For exampling order to add a new element to the array at the end
position in the array and the array is full then we copies the array into another array and
then we can add an element whereas if the linked list is full then we find the last node
and make it next to the new node.
There are four types of Linked List in Data Structures which are discussed below:
Suppose we have three nodes, and the addresses of these three nodes are 100, 200 and 300
respectively. The representation of three nodes as a linked list is shown in the below figure:
We can observe in the above figure that there are three different nodes having address 100,
200 and 300 respectively. The first node contains the address of the next node, i.e., 200, the
second node contains the address of the last node, i.e., 300, and the third node contains the
NULL value in its address part as it does not point to any node. The pointer that holds the
address of the initial node is known as a head pointer.
The linked list, which is shown in the above diagram, is known as a singly linked list as it
contains only a single link. In this list, only forward traversal is possible; we cannot
traverse in the backward direction as it has only one link in the list.
struct node
{
int data;
struct node *next;
}
Advantage
Dynamic size (no fixed limit like arrays)
Efficient insertion and deletion (especially in the middle)
Can implement complex data structures like stack, queue, graph
Disadvantage
Extra memory required for storing pointers
No direct/random access (need traversal)
Cache unfriendly (not stored in contiguous memory)
Suppose we have three nodes, and the address of these nodes are 100, 200 and 300,
respectively. The representation of these nodes in a doubly-linked list is shown below:
As we can observe in the above figure, the node in a doubly-linked list has two address
parts; one part stores the address of the next while the other part of the node stores the
previous node's address. The initial node in the doubly linked list has the NULL value in
the address part, which provides the address of the previous node.
struct node
{
int data;
struct node *next;
struct node *prev;
}
In the above representation, we have defined a user-defined structure named a node with
three members, one is data of integer type, and the other two are the pointers, i.e., next and
prev of the node type. The next pointer variable holds the address of the next node, and the
prev pointer holds the address of the previous node. The type of both the pointers, i.e., next
and prev is struct node as both the pointers are storing the address of the node of the struct
node type.
struct node
{
int data;
struct node *next;
}
A circular linked list is a sequence of elements in which each node has a link to the next
node, and the last node is having a link to the first node. The representation of the circular
linked list will be similar to the singly linked list.
struct node
{
int data;
struct node *next;
struct node *prev;
}
Traversal (Forward & Visit all nodes starting from head in forward or backward direction
Backward) (since it’s circular, traversal can continue indefinitely).
Insert a new node before the head node and update head, previous,
Insertion at Beginning
and next pointers.
Insertion at End Insert a new node after the last node and update circular links.
Delete the head node and update the head to the next node, also fix
Deletion at Beginning
circular links.
Delete the last node and update circular pointers to maintain the
Deletion at End
structure.
Count the number of nodes in the list by traversing once through the
Count
circular structure.