Data Structure Lecture Note
Data Structure Lecture Note
A data structure is a specialized format for organizing, processing, retrieving and storing data.
There are several basic and advanced types of data structures, all designed to arrange data to suit
a specific purpose. Data structures make it easy for users to access and work with the data they
need in appropriate ways. Most importantly, data structures frame the organization of information
so that machines and humans can better understand it.
In computer science and computer programming, a data structure may be selected or designed to
store data for the purpose of using it with various algorithms. In some cases, the algorithm's basic
operations are tightly coupled to the data structure's design. Each data structure contains
information about the data values, relationships between the data and -- in some cases -- functions
that can be applied to the data.
For instance, in an object-oriented programming language, the data structure and its associated
methods are bound together as part of a class definition. In non-object-oriented languages, there
may be functions defined to work with the data structure, but they are not technically part of the
data structure.
Typical base data types, such as integers or floating-point values, that are available in most
computer programming languages are generally insufficient to capture the logical intent for data
processing and use. Yet applications that ingest, manipulate and produce information must
understand how data should be organized to simplify processing. Data structures bring together
the data elements in a logical way and facilitate the effective use, persistence and sharing of data.
They provide a formal model that describes the way the data elements are organized.
Data structures are the building blocks for more sophisticated applications. They are designed by
composing data elements into a logical unit representing an abstract data type that has relevance
to the algorithm or application. An example of an abstract data type is a "customer name" that is
composed of the character strings for "first name," "middle name" and "last name."
In general, data structures are used to implement the physical forms of abstract data types. Data
structures are a crucial part of designing efficient software. They also play a critical role in
algorithm design and how those algorithms are used within computer programs.
Early programming languages -- such as Fortran, C and C++ -- enabled programmers to define
their own data structures. Today, many programming languages include an extensive collection of
built-in data structures to organize code and information. For example, Python lists and
dictionaries, and JavaScript arrays and objects are common coding structures used for storing and
retrieving information.
Software engineers use algorithms that are tightly coupled with the data structures -- such as lists,
queues and mappings from one set of values to another. This approach can be fused in a variety of
applications, including managing collections of records in a relational database and creating an
index of those records using a data structure called a binary tree.
Some examples of how data structures are used include the following:
Storing data. Data structures are used for efficient data persistence, such as specifying the
collection of attributes and corresponding structures used to store records in a database
management system.
Managing resources and services. Core operating system (OS) resources and services are
enabled through the use of data structures such as linked lists for memory allocation, file
directory management and file structure trees, as well as process scheduling queues.
Data structures are often classified by their characteristics. The following three characteristics are
examples:
1. Linear or non-linear. This characteristic describes whether the data items are arranged in
sequential order, such as with an array, or in an unordered sequence, such as with a graph.
2. Homogeneous or heterogeneous. This characteristic describes whether all data items in
a given repository are of the same type. One example is a collection of elements in an array,
or of various types, such as an abstract data type defined as a structure in C or a class
specification in Java.
3. Static or dynamic. This characteristic describes how the data structures are compiled.
Static data structures have fixed sizes, structures and memory locations at compile time.
Dynamic data structures have sizes, structures and memory locations that can shrink or
expand, depending on the use.
Data types
If data structures are the building blocks of algorithms and computer programs, the primitive -- or
base -- data types are the building blocks of data structures. The typical base data types include
the following:
Boolean, which stores logical values that are either true or false.
integer, which stores a range on mathematical integers -- or counting numbers. Different
sized integers hold a different range of values -- e.g., a signed 8-bit integer holds values
from -128 to 127, and an unsigned long 32-bit integer holds values from 0 to 4,294,967,295.
Floating-point numbers, which store a formulaic representation of real numbers.
Fixed-point numbers, which are used in some programming languages and hold real
values but are managed as digits to the left and the right of the decimal point.
Character, which uses symbols from a defined mapping of integer values to symbols.
Pointers, which are reference values that point to other values.
When choosing a data structure for a program or application, developers should consider the
answers to the following three questions:
1. Supported operations. What functions and operations does the program need?
2. Computational complexity. What level of computational performance is tolerable? For
speed, a data structure whose operations execute in time linear to the number of items
managed -- using Big O Notation: O(n) -- will be faster than a data structure whose
operations execute in time proportional to the square of the number of items managed --
O(n^2).
3. Programming elegance. Are the organization of the data structure and its functional
interface easy to use?
Linked lists are best if a program is managing a collection of items that don't need to be
ordered, constant time is required for adding or removing an item from the collection and
increased search time is OK.
Stacks are best if the program is managing a collection that needs to support a LIFO order.
Queues should be used if the program is managing a collection that needs to support a
FIFO order.
Binary trees are good for managing a collection of items with a parent-child relationship,
such as a family tree.
Binary search trees are appropriate for managing a sorted collection where the goal is to
optimize the time it takes to find specific items in the collection.
Graphs work best if the application will analyze connectivity and relationships among a
collection of individuals in a social media network.
Data Structures
Data structures refer to the actual implementation of organizing, storing, and manipulating data
in a computer‟s memory. They are concrete and specific, defining how data is arranged and
accessed. Examples include arrays, linked lists, stacks, queues, and hash tables. Data structures
are crucial for optimizing operations on data, as they determine how quickly and efficiently tasks
can be performed.
An Abstract Data Type (ADT) is a programming concept that defines a high-level view of a data
structure, without specifying the implementation details. In other words, it is a blueprint for
creating a data structure that defines the behavior and interface of the structure, without specifying
how it is implemented.
An ADT in the data structure can be thought of as a set of operations that can be performed on a
set of values. This set of operations actually defines the behavior of the data structure, and they are
used to manipulate the data in a way that suits the needs of the program.
ADTs are often used to abstract away the complexity of a data structure and to provide a simple
and intuitive interface for accessing and manipulating the data. This makes it easier for
programmers to reason about the data structure, and to use it correctly in their programs.
Examples of abstract data type in data structures are List, Stack, Queue, etc.
List ADT
Lists are linear data structures that hold data in a non-continuous structure. The list is made up of
data storage containers known as "nodes." These nodes are linked to one another, which means
that each node contains the address of another block. All of the nodes are thus connected to one
another via these links. You can discover more about lists in this article: Linked List Data Structure.
front(): returns the value of the node present at the front of the list.
back(): returns the value of the node present at the back of the list.
push_front(int val): creates a pointer with value = val and keeps this pointer to the front
of the linked list.
push_back(int val): creates a pointer with value = val and keeps this pointer to the back
of the linked list.
pop_front(): removes the front node from the list.
pop_back(): removes the last node from the list.
empty(): returns true if the list is empty, otherwise returns false.
size(): returns the number of nodes that are present in the list.
Stack ADT
A stack is a linear data structure that only allows data to be accessed from the top. It simply has
two operations: push (to insert data to the top of the stack) and pop (to remove data from the stack).
(used to remove data from the stack top).
Some of the most essential operations defined in Stack ADT are listed below.
Queue ADT
A queue is a linear data structure that allows data to be accessed from both ends. There are two
main operations in the queue: push (this operation inserts data to the back of the queue) and pop
(this operation is used to remove data from the front of the queue).
Some of the most essential operations defined in Queue ADT are listed below.
front(): returns the value of the node present at the front of the queue.
back(): returns the value of the node present at the back of the queue.
push(int val): creates a node with value = val and puts it at the front of the queue.
pop(): removes the node from the rear of the queue.
empty(): returns true if the queue is empty, otherwise returns false.
size(): returns the number of nodes that are present in the queue.
Provides abstraction, which simplifies the complexity of the data structure and allows
users to focus on the functionality.
Overhead: Using ADTs may result in additional overhead due to the need for abstraction
and encapsulation.
Limited control: ADTs can limit the level of control that a programmer has over the data
structure, which can be a disadvantage in certain scenarios.
Performance impact: Depending on the specific implementation, the performance of an
ADT may be lower than that of a custom data structure designed for a specific application.
Primitive data types, also known as basic data types or fundamental data types, are the simplest
data types provided by a programming language. They are the building blocks for constructing
more complex data structures and represent basic values that can be manipulated directly by the
computer's hardware. Primitive data types are typically supported directly by the language and are
not composed of other data types.
Primitive Data Types: Primitive data types are fundamental data types provided by a
programming language to represent basic values. They are directly supported by the language and
typically correspond to data types natively supported by the computer's hardware.
Primitive data types are used to store simple values such as integers, floating-point numbers,
characters, and boolean values. They are often classified based on the kind of data they represent
and the amount of memory they occupy.
Primitive data structures have several key features that make them useful in programming. Here
are the details of each key feature:
1. Size: Primitive data structures have a fixed size, which means that they take up a
predictable amount of memory. For example, an integer in most programming languages
takes up four bytes of memory, regardless of the value it represents.
2. Speed: Primitive data structures are simple, which means that they can be processed
quickly by the computer. Because they have a fixed size and format, the computer can
easily perform calculations and operations on them without needing to spend extra time or
resources on interpretation or conversion.
3. Memory Efficiency: Primitive data structures use a minimal amount of memory, which is
important when working with large amounts of data. Because they have a fixed size,
primitive data types are more efficient in terms of memory usage than complex data
structures.
4. Portability: Primitive data structures are typically the same across different programming
languages and platforms, which makes code more portable. This means
Let us now look at some of the most often-used primitive data structures.
1. Bool
The bool data type is a primitive data structure that represents a logical value, which can be either
true or false. In most programming languages, the bool data type has a fixed size of one byte.
Syntax in C++:
bool a = true;
Syntax in Java:
boolean a = false;
Syntax in Python:
a = True
2. Byte
The byte data type is a primitive data structure that represents an 8-bit signed integer. In Java, the
byte data type is represented using the keyword "byte" and has a size of 1 byte. It is useful for
storing small integers in memory and is often used in situations where memory usage is critical.
Syntax in Java:
In this example, "myByteVariable" is a byte variable that has been assigned the value 127. Note
that the range of values that can be stored in a byte is -128 to 127, inclusive.
The char data type is a primitive data structure that represents a single character, such as a letter,
digit, or symbol. The char data type has a fixed size of two bytes.
Syntax in C++:
Syntax in Java:
4. Int
The int data type is a primitive data structure that is used to represent integer values. It can store
both positive and negative whole numbers, such as 0, 1, -2, 100, etc. The int data type has a fixed
size of 4 bytes in memory.
Syntax in C++:
Syntax in Java:
Syntax in Python:
myInt = 30
5. Float
The float data type is a primitive data structure that is used to represent floating-point numbers. It
is used to store real numbers that require a decimal point, such as 3.14 or -2.5. The float data type
has a fixed size of 4 bytes in memory.
Syntax in C++:
Syntax in Java:
In this example, "myFloat" is a float variable that has been assigned the value 5.20. The "f" at the
end of the value indicates that it should be treated as a float rather than a double.
Syntax in Python:
myFloat = 7.14
6. Double
The double data type is similar to the float data type but can store larger decimal values with more
precision. It is a primitive data structure that is used to represent floating-point numbers with
double precision. The double data type has a fixed size of 8 bytes in memory.
Syntax in C++:
Syntax in Java:
7. Long
The long data type is a primitive data structure that is used to represent integer values that require
more memory than an int. It can store larger whole numbers, such as 2147483647 or - 2147483648.
The long data type has a fixed size of 8 bytes in memory.
Syntax in C++:
Syntax in Java:
8. Short
The short data type is a primitive data structure that is used to represent integer values that require
less memory than an int. It can store smaller whole numbers, such as 32767 or -32768. The short
data type takes only 2 bytes in memory.
Syntax in C++:
Syntax in Java:
9. Pointer
A pointer is a primitive data structure that stores the memory address of another variable or data
structure. It is a powerful tool for memory management and data manipulation in C and C++.
Pointers are not available in Java or Python.
In C and C++, a pointer is defined using the "*" symbol before the variable name. Here is an
example of how to declare and use a pointer in C++:
In this example, "myPointer" is a pointer variable that has been assigned the memory address of
"myInt" using the "&" symbol.
These primitive data types serve as the basic building blocks for storing and manipulating data in
programming languages. They are used extensively in variable declarations, function parameters,
1. Integer:
o Integers are typically stored using a fixed number of bits, which determines their
range and precision.
o Common integer data types include int, long, short, and byte.
o The memory representation of integers uses binary encoding, where each bit
represents a binary digit (0 or 1).
Example in C++:
cpp
int number = 42; // 32 bits (4 bytes)
Memory Representation:
In this example, the integer 42 is stored in memory using 32 bits, with each bit representing a
binary digit.
2. Floating-Point:
o Floating-point numbers are stored using a binary representation that includes a
sign bit, a significand (or mantissa), and an exponent.
o Common floating-point data types include float and double.
o The memory representation of floating-point numbers follows the IEEE 754
standard, which defines how numbers are encoded in binary format.
Example in Java:
java
double value = 3.14159; // 64 bits (8 bytes)
Memory Representation:
In this example, the floating-point number 3.14159 is stored in memory using 64 bits, following
the IEEE 754 standard.
3. Character:
o Characters are typically stored using a fixed number of bits, where each bit
represents a character from a character set (e.g., ASCII or Unicode).
o Character data types include char in C/C++ and Java.
o The memory representation of characters depends on the character encoding used
by the system.
Example in C:
c
char letter = 'A'; // 8 bits (1 byte)
01000001
In this example, the character 'A' is stored in memory using 8 bits, following the ASCII character
encoding.
4. Boolean:
o Booleans are typically stored using a single bit, where true is represented by 1 and
false is represented by 0.
o Boolean data types include bool in C++ and C#, and boolean in Java.
Example in C#:
csharp
bool isValid = true; // 1 bit
Memory Representation:
A linear data structure called an array contains elements of the same data type in contiguous and
nearby memory regions. Arrays operate using an index system with values ranging from 0 to (n-
1), where n is the array‟s size.
It would be more challenging to declare and maintain track of all the variables if more students
joined. Arrays were introduced as a solution to this issue.
One-Dimensional Arrays:
A one-dimensional array can be thought of as a row where elements are kept one after the other.
Multi-Dimensional Arrays:
There are two different types of these multidimensional arrays. Those are:
Two-Dimensional Arrays:
Three-Dimensional Arrays:
The size of the arrays is often the argument in square bracket definitions for arrays.
Method 1:
Method 2:
Method 3:
int n;
scanf(“%d”,&n);
int arr[n];
scanf(“%d”,&arr[i]);
Method 4:
int arr[5];
arr[0]=1;
arr[1]=2;
arr[2]=3;
arr[3]=4;
arr[4]=5;
C
#include<stdio.h>
int main()
{
int a[5] = {2, 3, 5, 7, 11};
printf("%d\n",a[0]); // we are accessing
printf("%d\n",a[1]);
printf("%d\n",a[2]);
printf("%d\n",a[3]);
printf("%d",a[4]);
return 0;
}
Output
2
3
5
7
11
Traversal operation
#include <stdio.h>
void main() {
int Arr[5] = {18, 30, 15, 70, 12};
int i;
printf("Elements of the array are:\n");
for(i = 0; i<5; i++) {
printf("Arr[%d] = %d, ", i, Arr[i]);
}
}
Output
Insertion operation
One or more members are added to the array by using this method. An element can be added to
the array at any index, at the beginning or end, or both, depending on the specifications. Let's see
the implementation of adding an element to the array right away.
#include <stdio.h>
int main()
{
int arr[20] = { 18, 30, 15, 70, 12 };
int i, x, pos, n = 5;
printf("Array elements before insertion\n");
for (i = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
x = 50; // element to be inserted
Output
Deletion operation
As the name suggests, this operation rearranges every element in the array after removing one
element from it.
#include <stdio.h>
void main() {
int arr[] = {18, 30, 15, 70, 12};
int k = 30, n = 5;
int i, j;
printf("Given array elements are :\n");
for(i = 0; i<n; i++) {
printf("arr[%d] = %d, ", i, arr[i]);
}
j = k;
Output
Search operation
Using the value or index, this operation is used to search for an element in the array.
#include <stdio.h>
void main() {
int arr[5] = {18, 30, 15, 70, 12};
int item = 70, i, j=0 ;
printf("Given array elements are :\n");
for(i = 0; i<5; i++) {
printf("arr[%d] = %d, ", i, arr[i]);
}
printf("\nElement to be searched = %d", item);
while( j < 5){
if( arr[j] == item ) {
break;
Output
Update operation
This action is used to update an array element that is already present and is located at the
specified index.
#include <stdio.h>
void main() {
int arr[5] = {18, 30, 15, 70, 12};
int item = 50, i, pos = 3;
printf("Given array elements are :\n");
for(i = 0; i<5; i++) {
printf("arr[%d] = %d, ", i, arr[i]);
}
arr[pos-1] = item;
printf("\nArray elements after updation :\n");
for(i = 0; i<5; i++) {
printf("arr[%d] = %d, ", i, arr[i]);
}
}
Output
The following table lists the time and space complexity of several array operations..
Advantages of Array
Disadvantages of Array
The array is uniform. This implies that it can store elements with similar data types.
An array has static memory allocation, meaning that its size cannot be changed.
If we store less elements than the given size, memory will be wasted.
Limitations of arrays
Fixed size in traditional arrays, making resizing difficult.
Contiguous memory allocation can lead to memory fragmentation.
Inefficient insertion or deletion operations in fixed-size arrays
A linked list is a linear data structure which can store a collection of "nodes" connected together
via links i.e. pointers. Linked lists nodes are not stored at a contiguous location, rather they are
linked using pointers to the different memory locations. A node consists of the data value and a
pointer to the address of the next node within the linked list.
A linked list is a dynamic linear data structure whose memory size can be allocated or de- allocated
at run time based on the operation insertion or deletion, this helps in using system memory
efficiently. Linked lists can be used to implement various data structures like a stack, queue, graph,
hash maps, etc.
This representation of a linked list depicts that each node consists of two fields. The first field
consists of data, and the second field consists of pointers that point to another node.
A linked list starts with a head node which points to the first node. Every node consists of data
which holds the actual data (value) associated with the node and a next pointer which holds the
memory address of the next node in the linked list. The last node is called the tail node in the list
which points to null indicating the end of the list.
struct node
int data;
};
struct node * n;
It is a declaration of a node that consists of the first variable as data and the next as a pointer,
which will keep the address of the next node.
Here you need to use the malloc function to allocate memory for the nodes dynamically.
A singly linked list is the most common type of linked list. Each node has data and an address
field that contains a reference to the next node.
Advantages:
Disadvantages:
1. No Random Access: Unlike arrays, singly linked lists do not support random access to
elements. Traversal must start from the head node, making operations like accessing the
nth element less efficient.
2. Extra Memory Overhead: Each node in a singly linked list requires extra memory for
storing the pointer to the next node, leading to higher memory overhead compared to arrays
for storing the same amount of data.
3. Traversal Overhead: Traversing a singly linked list requires traversing each node
sequentially from the head to the desired node, which can be slower compared to direct
access in arrays.
Use Cases:
1. Dynamic Data Structures: Singly linked lists are suitable for implementing dynamic data
structures where the size of the data structure changes frequently during runtime.
2. Frequent Insertions and Deletions: Applications that involve frequent insertions and
deletions, especially at the beginning of the list, can benefit from using singly linked lists
due to their efficient insertion and deletion operations.
3. Implementation of Stacks and Queues: Singly linked lists serve as the underlying data
structure for implementing stack and queue data structures, where elements are inserted
and removed from one end of the list.
1. Define a structure for a node with two components: data and a pointer to the next node.
2. Define a structure for the linked list with a pointer to the head node.
3. Initialize the head pointer to NULL, indicating an empty list.
4. Define functions to perform various operations on the linked list:
a. Insertion:
i. Create a new node with the given data.
ii. If the list is empty, set the new node as the head node.
iii. Otherwise, traverse the list to the last node.
iv. Set the next pointer of the last node to the new node.
b. Deletion:
i. If the list is empty, return an error.
ii. If the node to be deleted is the head node, update the head pointer to point to the next
node.
iii. Otherwise, traverse the list to find the node before the node to be deleted.
iv. Update the next pointer of the previous node to skip over the node to be deleted.
v. Free the memory allocated to the deleted node.
c. Traversal:
i. Start from the head node.
ii. Traverse each node in the list using the next pointer until NULL is reached.
iii. Process each node as required.
5. Provide functions to create and destroy the linked list.
6. Test the implementation with various scenarios to ensure correctness.
End Algorithm
1. Bidirectional Traversal: Unlike singly linked lists, doubly linked lists allow traversal in
both directions, forward and backward.
2. Insertion and Deletion Efficiency: Insertions and deletions at the beginning, end, or
middle of the list can be done efficiently with constant time complexity, O(1).
3. Memory Overhead: Each node contains two pointers, one for the next node and one for
the previous node, allowing efficient memory utilization and easy navigation.
1. Increased Memory Usage: The use of an additional pointer per node increases the
memory overhead compared to singly linked lists.
2. Complexity: Implementing and maintaining doubly linked lists can be more complex than
singly linked lists due to the bidirectional nature of the structure.
3. Overhead for Traversal: While doubly linked lists offer bidirectional traversal, this
capability comes with the cost of additional memory overhead and potentially increased
complexity.
1. Define a structure for a node with three components: data, a pointer to the next node, and a
pointer to the previous node.
2. Define a structure for the doubly linked list with pointers to the head and tail nodes.
3. Initialize the head and tail pointers to NULL, indicating an empty list.
4. Define functions to perform various operations on the doubly linked list:
a. Insertion:
i. Create a new node with the given data.
ii. If the list is empty, set the new node as both the head and tail node.
iii. Otherwise, set the next pointer of the new node to the current head node.
iv. Set the previous pointer of the current head node to the new node.
v. Update the head pointer to point to the new node.
b. Deletion:
i. If the list is empty, return an error.
ii. If the node to be deleted is the head node, update the head pointer to point to the next
node.
iii. If the node to be deleted is the tail node, update the tail pointer to point to the previous
node.
iv. Otherwise, traverse the list to find the node to be deleted.
v. Update the next pointer of the previous node to skip over the node to be deleted.
vi. Update the previous pointer of the next node to skip back to the previous node.
vii. Free the memory allocated to the deleted node.
c. Traversal:
i. Start from the head node.
ii. Traverse each node in the list using the next pointer until NULL is reached.
End Algorithm
The circular linked list is extremely similar to the singly linked list. The only difference is that
the last node is connected with the first node, forming a circular loop in the circular linked list.
1. Efficient Memory Utilization: Circular linked lists can efficiently utilize memory since
each node only requires a pointer to the next node, and the last node points back to the first
node, forming a loop.
2. Dynamic Size: Circular linked lists can dynamically grow and shrink during runtime by
adding or removing nodes without the need to resize the structure.
3. Circular Traversal: Circular linked lists allow for circular traversal, meaning that after
reaching the last node, the traversal continues from the first node, making it suitable for
applications where continuous looping is required.
1. Circular Buffer: Circular linked lists are used to implement circular buffers or queues,
where data items are stored in a fixed-size buffer and accessed in a circular manner.
2. Round-Robin Scheduling: Circular linked lists are employed in operating system
scheduling algorithms like round-robin scheduling, where processes are scheduled in a
circular order.
3. Game Development: Circular linked lists can be utilized in game development for
managing game objects or entities that need to loop through a sequence of actions or events.
1. Define a structure for a node with two components: data and a pointer to the next node.
2. Define a structure for the circular linked list with a pointer to the head node.
3. Initialize the head pointer to NULL, indicating an empty list.
4. Define functions to perform various operations on the circular linked list:
a. Insertion:
i. Create a new node with the given data.
ii. If the list is empty, set the new node as both the head node and the last node.
iii. Otherwise, traverse the list to reach the last node.
iv. Set the next pointer of the last node to point to the new node.
v. Set the next pointer of the new node to point back to the head node.
vi. Update the head pointer to point to the new node if inserting at the beginning.
b. Deletion:
i. If the list is empty, return an error.
ii. If there is only one node in the list, free the memory allocated to the node and set the
head pointer to NULL.
End Algorithm
The next node's next pointer will point to the first node to form a singly linked list.
The previous pointer of the first node keeps the address of the last node to form a doubly-
linked list.
The basic operations in the linked lists are insertion, deletion, searching, display, and deleting an
element at a given key. These operations are performed on Singly Linked Lists as given below −
Adding a new node in linked list is a more than one step activity. We shall learn this with diagrams
here. First, create a node using the same structure and find the location where it has to be inserted.
Now, the next node at the left should point to the new node.
This will put the new node in the middle of the two. The new list should look like this −
Insertion at Beginning
Algorithm
1. START
2. Create a node to store the data
3. Check if the list is empty
4. If the list is empty, add the data to the node and
assign the head pointer to it.
5. If the list is not empty, add the data to a node and link to the
current head. Assign the head to the newly added node.
6. END
Example
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct node {
int data;
struct node *next;
};
struct node *head = NULL;
struct node *current = NULL;
//create a link
struct node *lk = (struct node*) malloc(sizeof(struct node));
lk->data = data;
// print list
printList();
}
Insertion at Ending
Algorithm
1. START
2. Create a new node and assign the data
3. Find the last node
4. Point the last node to new node
5. END
In this operation, we are adding an element at any position within the list.
Algorithm
1. START
2. Create a new node and assign data to it
3. Iterate until the node at position is found
4. Point first to new first node
5. END
Example
Deletion is also a more than one step process. We shall learn with pictorial representation. First,
locate the target node to be removed, by using searching algorithms.
This will remove the link that was pointing to the target node. Now, using the following code, we
will remove what the target node is pointing at.
We need to use the deleted node. We can keep that in memory otherwise we can simply
deallocate memory and wipe off the target node completely.
Deletion in linked lists is also performed in three different ways. They are as follows −
Deletion at Beginning
In this deletion operation of the linked, we are deleting an element from the beginning of the list.
For this, we point the head to the second node.
Algorithm
1. START
2. Assign the head pointer to the next node in the list
3. END
This operation is a thorough one. We need to make the last node to be pointed by the head node
and reverse the whole linked list.
First, we traverse to the end of the list. It should be pointing to NULL. Now, we shall make it point
to its previous node −
Except the node (first node) pointed by the head node, all nodes should point to their predecessor,
making them their new successor. The first node will point to NULL.
We'll make the head node point to the new first node by using the temp node.
Algorithm
1. START
2. We use three pointers to perform the reversing:
prev, next, head.
3. Point the current node to head and assign its next value to
the prev node.
4. Iteratively repeat the step 3 for all the nodes in the list.
5. Assign head to the prev node.
Searching for an element in the list using a key element. This operation is done in the same way
as array search; comparing every element in the list with the key element given.
Algorithm
1 START
2 If the list is not empty, iteratively check if the list
contains the key
3 If the key element is not present in the list, unsuccessful
search
4 END
Linked lists and arrays are both data structures used to store collections of elements, but they have
different characteristics and are suitable for different scenarios. Let's compare them across various
aspects:
1. Memory Allocation:
o Arrays: Contiguous block of memory is allocated to store elements. Memory is
allocated at once, and the size typically cannot be changed dynamically.
o Linked Lists: Elements are stored in nodes, where each node contains the data and
a pointer/reference to the next node. Nodes can be scattered in memory, and
memory is allocated dynamically as nodes are added.
2. Insertion and Deletion:
o Arrays: Insertion and deletion operations can be inefficient, especially in the
middle, as elements may need to be shifted.
o Linked Lists: Insertion and deletion operations are generally efficient, as they
involve changing pointers to rearrange the structure.
3. Access Time:
o Arrays: Random access to elements is fast using indices. Access time is O(1).
The stack data structure is a linear data structure that accompanies a principle known as LIFO
(Last In First Out) or FILO (First In Last Out). Real-life examples of a stack are a deck of cards,
piles of books, piles of money, and many more.
This example allows you to perform operations from one end only, like when you insert and
remove new books from the top of the stack. It means insertion and deletion in the stack data
structure can be done only from the top of the stack. You can access only the top of the stack at
any given point in time.
Now, assume that you have a stack of books. You can only see the top, i.e., the top-most book,
namely 40, which is kept top of the stack.
If you want to insert a new book first, namely 50, you must update the top and then insert a new
text. And if you want to access any other book other than the topmost book that is 40, you first
remove the topmost book from the stack, and then the top will point to the next topmost book.
There following are some operations that are implemented on the stack.
Push Operation
Push operation involves inserting new elements in the stack. Since you have only one end to insert
a unique element on top of the stack, it inserts the new element at the top of the stack.
Pop Operation
Pop operation refers to removing the element from the stack again since you have only one end to
do all top of the stack. So removing an element from the top of the stack is termed pop operation.
isFull()
begin
If
return true
else
return false
else if
end
Bool isFull()
if(top == maxsize)
return true;
else
return false;
begin
If
topless than 1
return true
else
return false
else if
end
Bool isEmpty()
if(top = = -1)
return true;
else
return false;
Push Operation
Step 5: Success
end if
top ->top+1;
end
top = top + 1;
stack[top] = item;
else {
printf(“stack is full”);
Pop Operation
Step 5: Success
return null
end if
Return item;
end
If isEmpty()) {
item = stack[top];
top = top - 1;
else{
printf(“stack if empty”);
Peek Operation
begin to peek
return stack[top];
end
int peek()
return stack[top];
You can perform the implementation of stacks in data structures using two data structures that
are an array and a linked list.
Linked-List: Every new element is inserted as a top element in the linked list
implementation of stacks in data structures. That means every newly inserted element is
pointed to the top. Whenever you want to remove an element from the stack, remove the
node indicated by the top, by moving the top to its previous node in the list.
There are three types of expression that you use in programming, they are:
Infix Expression: An infix expression is a single letter or an operator preceded by one single
infix string followed by another single infix string.
X
Prefix Expression: A prefix expression is a single letter or an operator followed by two prefix
strings.
X
+XY
++XY-AB
Postfix Expression: A postfix expression (also called Reverse Polish Notation) is a single letter or
an operator preceded by two postfix strings.
X
XY+
XY+CD-+
Similarly, the stack is used to evaluate these expressions and convert these expressions like infix
to prefix or infix to postfix.
2. Backtracking
To solve the optimization problem with backtracking, you have multiple solutions; it does not
matter if it is correct. While finding all the possible solutions in backtracking, you store the
previously calculated problems in the stack and use that solution to resolve the following issues.
The N-queen problem is an example of backtracking, a recursive algorithm where the stack is used
to solve this problem.
3. Function Call
Whenever you call one function from another function in programming, the reference of calling
function stores in the stack. When the function call is terminated, the program control moves back
to the function call with the help of references stored in the stack.
4. Parentheses Checking
Stack in data structures is used to check if the parentheses like ( ), { } are valid or not in
programing while matching opening and closing brackets are balanced or not.
So it stores all these parentheses in the stack and controls the flow of the program.
For e.g ((a + b) * (c + d)) is valid but {{a+b})) *(b+d}] is not valid.
5. String Reversal
Another exciting application of stack is string reversal. Each character of a string gets stored in
the stack.
The string's first character is held at the bottom of the stack, and the last character of the string is
held at the top of the stack, resulting in a reversed string after performing the pop operation.
6. Syntax Parsing
Since many programming languages are context-free languages, the stack is used for syntax
parsing by many compilers.
7. Memory Management
Memory management is an essential feature of the operating system, so the stack is heavily used
to manage memory.
Queue in data structures is a linear collection of different data types which follow a specific order
while performing various operations. It can only be modified by the addition of data entities at
one end or the removal of data entities at another. By convention, the end where insertion is
performed is called Rear, and the end at which deletion takes place is known as the Front.
These constraints of queue make it a First-In-First-Out (FIFO) data structure, i.e., the data element
inserted first will be accessed first, and the data element inserted last will be accessed last. This is
equivalent to the requirement that once an additional data element is added, all previously added
elements must be removed before the new element can be removed. That‟s why more abstractly,
a queue in a data structure is considered being a sequential collection.
Once you are clear with the key terms related to a queue data structure, you will now look at its
representation details.
Queue Representation
Before dealing with the representation of a queue, examine the real-life example of the queue to
understand it better. The movie ticket counter is an excellent example of a queue where the
customer that came first will be served first. Also, the barricades of the movie ticket counter stop
in-between disruption to attain different operations at different ends.
A queue can be implemented using Arrays, Linked-lists, Pointers, and Structures. The
implementation using one-dimensional arrays is the easiest method of all the mentioned methods.
With this understanding of queue representation, look at the different operations that can be
performed on the queues in data structures.
Unlike arrays and linked lists, elements in the queue cannot be operated from their respective
locations. They can only be operated at two data pointers, front and rear. Also, these operations
involve standard procedures like initializing or defining data structure, utilizing it, and then wholly
erasing it from memory. Here, you must try to comprehend the operations associated with queues:
When you define the queue data structure, it remains empty as no element is inserted into it. So,
both the front and rear pointer should be set to -1 (Null memory space). This phase is known as
data structure declaration in the context of programming.
First, understand the operations that allow the queue to manipulate data elements in a hierarchy.
Enqueue() Operation
The following steps should be followed to insert (enqueue) data element into a queue -
Obtaining data from the queue comprises two subtasks: access the data where the front is pointing
and remove the data after access. You should take the following steps to remove data from the
queue -
Now that you have dealt with the operations that allow manipulation of data entities, you will
encounter supportive functions of the queues -
Peek() Operation
This function helps in extracting the data element where the front is pointing without removing it
from the queue. The algorithm of Peek() function is as follows-
isFull() Operation
This function checks if the rear pointer is reached at MAXSIZE to determine that the queue is full.
The following steps are performed in the isFull() operation -
isNull() Operation
Step 1: Check if the rear and front are pointing to null memory space, i.e., -1.
Step 2: If they are pointing to -1, return “Queue is empty.”
Step 3: If they are not equal, return “Queue is not empty.”
Now that you have covered all the queue operations, you will discover a few applications of the
queue.
Applications of Queue
Queue, as the name suggests, is utilized when you need to regulate a group of objects in order.
This data structure caters to the need for First Come First Serve problems in different software
applications. The scenarios mentioned below are a few systems that use the queue data structure
to serve their needs -
IO Buffers, files, and Pipes IO, where data is transferred asynchronously between two
processes
Semaphores in the operating system
FCFS (First Come First Serve) scheduling
Spooling in printers
Parameter for
Stack Queue
Comparison
Operational
Follows Last In First Out or First Follows First In First Out or Last In Last
In Last Out principle Out principle
principle
Insertion and deletion both take Insertion occurs at the rear end, whereas
place from one end called as top deletion happens at the front end
Structure
Primary
push(x) and pop() are two Enqueue() and Dequeue() are two primary
primary queue operations data manipulation operations
operations
Condition to check If top == -1, the stack is If front == -1 && rear == -1, the queue is
empty state considered as empty considered as empty
Condition to check If top == MaxSize - 1, the stack If rear == Maxsize - 1, the queue is
full state is considered as full considered as full
Non-linear data structures are those where data items are not arranged in a sequential manner,
unlike linear data structures. In these data structures, elements are stored in a hierarchical or a
network-based structure that does not follow a sequential order. These data structures allow
efficient searching, insertion, and deletion of elements from the structure.
Trees
Graphs, etc.
A binary tree is a tree-type non-linear data structure with a maximum of two children for each
parent. Every node in a binary tree has a left and right reference along with the data element. The
node at the top of the hierarchy of a tree is called the root node. The nodes that hold other sub-
nodes are the parent nodes.
A parent node has two child nodes: the left child and right child. Hashing, routing data for network
traffic, data compression, preparing binary heaps, and binary search trees are some of the
applications that use a binary tree.
At every level of it, the maximum number allowed for nodes stands at 2i.
The height of a binary tree stands defined as the longest path emanating from a root node to the
tree‟s leaf node.
Say a binary tree placed at a height equal to 3. In that case, the highest number of nodes for this
height 3 stands equal to 15, that is, (1+2+4+8) = 15. In basic terms, the maximum node
number possible for this height h is (20 + 21 + 22+….2h) = 2h+1 -1.
Now, for the minimum node number that is possible at this height h, it comes as equal to h+1.
If there are a minimum number of nodes, then the height of a binary tree would stand aa maximum.
On the other hand, when there is a number of a node at its maximum, then the binary tree m height
will be minimum. If there exists around „n‟ number nodes in a binary tree, here is a calculation to
clarify the binary tree definition.
n+1 = 2h+1
log2(n+1) = log2(2h+1)
log2(n+1) = h+1
h = log2(n+1) – 1
n = h+1
h= n-1
There are three binary tree components. Every binary tree node has these three components
associated with it. It becomes an essential concept for programmers to understand these three
binary tree components:
1. Data element
2. Pointer to left subtree
3. Pointer to right subtree
There are various types of binary trees, and each of these binary tree types has unique
characteristics. Here are each of the binary tree types in detail:
It is a special kind of a binary tree that has either zero children or two children. It means that all
the nodes in that binary tree should either have two child nodes of its parent node or the parent
node is itself the leaf node or the external node.
In other words, a full binary tree is a unique binary tree where every node except the external node
has two children. When it holds a single child, such a binary tree will not be a full binary tree.
Here, the quantity of leaf nodes is equal to the number of internal nodes plus one. The equation is
like L=I+1, where L is the number of leaf nodes, and I is the number of internal nodes.
A complete binary tree is another specific type of binary tree where all the tree levels are filled
entirely with nodes, except the lowest level of the tree. Also, in the last or the lowest level of this
binary tree, every node should possibly reside on the left side. Here is the structure of a complete
binary tree:
A binary tree is said to be „perfect‟ if all the internal nodes have strictly two children, and every
external or leaf node is at the same level or same depth within a tree. A perfect binary tree having
height „h‟ has 2h – 1 node. Here is the structure of a perfect binary tree:
A binary tree is said to be „balanced‟ if the tree height is O(logN), where „N‟ is the number of
nodes. In a balanced binary tree, the height of the left and the right subtrees of each node should
A binary tree is said to be a degenerate binary tree or pathological binary tree if every internal
node has only a single child. Such trees are similar to a linked list performance-wise. Here is an
example of a degenerate binary tree:
Binary trees can also be grouped according to node values. The types of binary tree according to
node structure include the following:
Binary Search Tree: It is a special type of binary tree in which the value of the left node is
always smaller than the root node which is always smaller than the right node.
A Binary Search Tree is a hierarchical data structure that follows the principles of binary search.
Here's a breakdown:
1. Features:
o Ordered Structure: In a BST, each node has a key, and keys are arranged in a
specific order (e.g., in an ascending order from left to right).
o Efficient Searching: Due to its ordered structure, BSTs enable efficient searching,
insertion, and deletion operations. The time complexity for these operations is
O(log n) on average and O(n) in the worst case.
o Recursive Definition: Each subtree of a BST is also a BST, which makes it
convenient for recursive algorithms.
2. Implementation:
o Each node in a BST contains a key (value) and references to its left and right
children.
o The key of each node must be greater than all keys in its left subtree and less than
all keys in its right subtree.
o Insertions and deletions in a BST require maintaining the BST properties by
adjusting the structure as needed (e.g., through rotations).
3. Example: Let's say we have the following keys to be inserted into a BST: 50, 30, 70, 20,
40, 60, 80.
Inserting them in the order mentioned will create a BST where each node's left child
has a smaller value, and each node's right child has a larger value.
AVL Tree: An AVL binary tree in DSA is self-balanced. In such a tree, the difference between
the heights of the left and right subtrees for all nodes cannot be greater than one. So, the nodes in
the right as well as left subtrees of the AVL tree will be one or less than that. An AVL tree is a
self-balancing binary search tree. The balancing factor is either 1, -1, or 0.
Red-black Tree: A red-black tree is a type of binary tree that is self-balanced and each node in it
is either colored either red or black. The colors in a red black binary tree in data structure are useful
for keeping the whole tree balanced during deletions and insertions. The balance of a red black
tree won‟t be perfect. But these binary trees are perfect for bringing down the search time.
A B- Tree is a type of self-balanced search tree in data structures. These binary trees support
smooth access, deletion, and insertion of data items. B- trees are particularly common in file
systems and databases.
Among the different types of binary tree, a B- tree helps with efficient storage and retrieval of
large volumes of data. A fixed maximum degree or order is a key characteristic of a B- tree. This
fixed value helps determine the total number of child nodes in a parent node.
The nodes present in a B- binary tree can include several keys and child nodes. The keys of a B-
binary tree in algorithm design can help in indexing and locating data items.
B+ Tree
A binary tree in data structure can also be classified as B+, which is one variant of the B- tree.
Since a B+ tree comes with a fixed maximum degree, it enables efficient insertion, access, and
deletion of data items. But a B+ binary tree includes all data items inside the leaf nodes.
The internal nodes of a B+ binary tree only include keys for locating and indexing data items. Due
to this design, searches using a B+ tree will be a lot faster, and you will also be able to access data
items sequentially. Moreover, the leaf nodes of a binary tree remain together in a linked list.
Segment Tree
If you look into a binary tree and its types, you will come across one category called the segment
or statistic tree. This type of binary tree is usually responsible for storing information related to
different segments or intervals. With a segment tree, you will be able to perform querying of the
stored segments in a specific point.
Among the different types of binary tree in data structure, you will realize that a segment tree is
static. Therefore, you won‟t be able to modify the structure of a segment tree after it has been built.
If you want to read more on binary tree in data structure, you should learn about their
applications. Binary search trees are quite suitable for the following purposes:
Search Algorithms: An algorithm for binary search tree can efficiently find a specific
element. The search can be executed in O u(log n) time complexity, where n defines the
number of nodes. A binary search tree is often useful for quickly finding particular
elements in a sorted list.
Database Systems: With each node of a binary tree representing a record, data can be
stored in a database system. As a result, search operations may be completed quickly, and
the database system can manage massive volumes of data.
Decision Trees: Binary trees are a sort of machine learning technique that may be used to
create decision trees. These decision trees are highly useful for regression analysis and
classification.
File Systems: File systems can be implemented using binary trees, in which every node
corresponds to a directory or file. This enables quick and easy file system browsing and
searching.
Compression Algorithms: An algorithm for binary search tree in data structure can be
useful for Huffman coding. A compression algorithm is responsible for assigning variable-
length codes to characters according to their occurrence frequency in the input data.
Game AI: Game AI can be implemented using binary trees, where every node indicates a
potential move in the game. The optimal move can be found by the AI algorithm searching
the tree.
Sorting Algorithms: An algorithm of binary tree can also be used for efficient sorting. For
instance, the search tree sort and heap sort are quite beneficial.
Once you learn about a binary tree and its types, you should try to figure out the benefits of these
structures. Some key advantages of using a binary tree model include:
While a binary tree in data structure is highly beneficial, it also has some shortcomings. A few
reasons why binary trees might not be beneficial include:
Limited Structure: Every binary tree comes with a maximum of two children in each
node. While it is a boon in many ways and makes these structures memory efficient, it is
also a disadvantage. Due to their limited structure, binary trees cannot be used in certain
cases. For instance, some trees require each node to have more than two children. In that
case, a different tree format needs to be used in a data structure.
Space Inefficiency: Binary trees are not as space efficient as some other types of data
structures. Every node needs two child pointers. So, if it‟s a large binary tree, a significant
amount of memory will be required.
Insertion of an element
Removal of an element
Looking for an element
Deletion of an element
Traversing an element (You can perform four types of traversals in a binary tree structure.)
A binary tree is also suitable for performing a host of auxiliary operations. Some auxiliary
operations to implement on a binary tree include:
Tree Traversal techniques include various ways to visit all the nodes of the tree. Unlike linear
data structures (Array, Linked List, Queues, Stacks, etc) which have only one logical way to
traverse them, trees can be traversed in different ways.
Tree Traversal refers to the process of visiting or accessing each node of the tree exactly once in
a certain order. Tree traversal algorithms help us to visit and process all the nodes of the tree. Since
tree is not a linear data structure, there are multiple nodes which we can visit after visiting a certain
node. There are multiple tree traversal techniques which decide the order in which the nodes of the
tree are to be visited.
You can perform tree traversal in the data structure in numerous ways, unlike arrays, linked lists,
and other linear data structures, which are canonically traversed in linear order. You can walk
through them in either a depth-first search or a breadth-first search. In-order, pre-order, and post-
order are the three most frequent ways to go over them in depth-first.
Beyond these fundamental traversals, more complex or hybrid techniques, such as depth-limited
searches, such as iterative deepening depth-first search, are feasible.
The depth-first search (DFS) is a method of traversing or searching data structures composed of
trees or graphs.
To get to the recursion, go down one level. If the tree isn't empty, perform the three procedures
below in a specific order:
To duplicate the tree, you need to use pre-order traversal. Pre-order traversal is used to obtain a
prefix expression from an expression tree.
Inorder traversal gives nodes in non-decreasing order in binary search trees or BST. A version of
Inorder traversal with Inorder traversal reverse can access BST nodes in non-increasing order.
You can delete the tree using post-order traversal. The postfix expression of tree data structure can
also be obtained using post-order traversal.
BFS (Breadth-First Search) is a vertex-based method for determining the shortest path in a graph.
It employs a queue data structure, where first in, it follows first out. In BFS, it visits one vertex
and marks it at the time, after which it sees and queues its neighbors.
After understanding the types of tree traversal in data structures, you will now examine how to
implement tree traversal in data structures.
Pre-Order Traversal
preorder_traversal(node)
if (node == null)
return
visit(node)
preorder_traversal(node.left_subtree)
In-Order Traversal
inorder_traversal(node)
if (node == null)
return
inorder_traversal(node.left_subtree)
visit(node)
inorder_traversal(node.right_subtree)
In-Order Traversal
postorder_traversal(node)
if (node == null)
return
postorder_traversal(node.left_subtree)
postorder_traversal(node.right_subtree)
visit(node)
#include <stdio.h>
#include<conio.h>
#include <stdlib.h>
struct Node {
int x;
};
root->x = data;
return root;
node->x = data;
return node;
if (!t)
return;
release_tree(t->left_node);
release_tree(t->right_node);
A graph is a non linear data structure that consists of a set of vertices and a set of edges that connect
them together. In a graph, vertices represent entities, while edges represent the relationships
between them. Graphs are used to represent complex relationships between entities and are widely
used in computer science.
Types of Graph
Directed Graph: In the case of a directed graph, the edges have a direction that means,
the edge connecting vertex A to vertex B is different from the edge connecting vertex B to
vertex A.
Weighted Graph: In a weighted graph, edges have weights that represent the cost or
distance between the vertices.
In this structure, the elements are In this structure, the elements are
Basic arranged sequentially or linearly and arranged hierarchically or non-linear
attached to one another. manner.
Arrays, linked list, stack, queue are Trees and graphs are the types of a non-
Types
the types of a linear data structure. linear data structure.
Due to the linear organization, they Due to the non-linear organization, they
implementation
are easy to implement. are difficult to implement.
Each data item is attached to the Each item is attached to many other
Arrangement
previous and next items. items.
Memory In this, the memory utilization is not In this, memory is utilized in a very
utilization efficient. efficient manner.
The time complexity of linear data The time complexity of non-linear data
Time
structure increases with the increase structure often remains same with the
complexity
in the input size. increase in the input size.