UNIT - 1
ABSTRACT DATA TYPES (ADT):
Abstract Data type (ADT) is a type (or class) for objects whose
behaviour is defined by a set of values and a set of operations.
The definition of ADT only mentions what operations are to be
performed but not how these operations will be implemented. It
does not specify how data will be organized in memory and what
algorithms will be used for implementing the operations. It is called
“abstract” because it gives an implementation-independent
view. The process of providing only the essentials and hiding the
details is known as abstraction.
List ADT
• The data is generally stored in key sequence in a list which
has a head structure consisting
of count, pointers and address of compare function needed
to compare the data in the list.
• The data node contains the pointer to a data structure and
a self-referential pointer which points to the next node in the
list.
• The List ADT Functions is given below:
• get() – Return an element from the list at any given position.
• insert() – Insert an element at any position of the list.
• remove() – Remove the first occurrence of any element
from a non-empty list.
• removeAt() – Remove the element at a specified location
from a non-empty list.
• replace() – Replace an element at any position by another
element.
• size() – Return the number of elements in the list.
• isEmpty() – Return true if the list is empty, otherwise return
false.
• isFull() – Return true if the list is full, otherwise return false.
Stack ADT :-
• In Stack ADT Implementation instead of data being stored in
each node, the pointer to data is stored.
• The program allocates memory for the data and address is
passed to the stack ADT.
• The head node and the data nodes are encapsulated in the
ADT. The calling function can only see the pointer to the stack.
• The stack head structure also contains a pointer
to top and count of number of entries currently in stack.
• push() – Insert an element at one end of the stack called top.
• pop() – Remove and return the element at the top of the stack,
if it is not empty.
• peek() – Return the element at the top of the stack without
removing it, if the stack is not empty.
• size() – Return the number of elements in the stack.
• isEmpty() – Return true if the stack is empty, otherwise return
false.
• isFull() – Return true if the stack is full, otherwise return false.
Queue ADT:-
• The queue abstract data type (ADT) follows the basic design of
the stack abstract data type.
• Each node contains a void pointer to the data and the link
pointer to the next element in the queue. The program’s
responsibility is to allocate memory for storing the data.
• enqueue() – Insert an element at the end of the queue.
• dequeue() – Remove and return the first element of the
queue, if the queue is not empty.
• peek() – Return the element of the queue without removing it,
if the queue is not empty.
• size() – Return the number of elements in the queue.
• isEmpty() – Return true if the queue is empty, otherwise return
false.
• isFull() – Return true if the queue is full, otherwise return false.
Features of ADT:
Abstract data types (ADTs) are a way of encapsulating data and
operations on that data into a single unit. Some of the key features
of ADTs include:
• Abstraction: The user does not need to know the
implementation of the data structure only essentials are
provided.
• Better Conceptualization: ADT gives us a better
conceptualization of the real world.
• Robust: The program is robust and has the ability to catch
errors.
• Encapsulation: ADTs provide a way to encapsulate data and
operations into a single unit, making it easier to manage and
modify the data structure.
• Data Abstraction: ADTs provide a level of abstraction from
the implementation details of the data. Users only need to
know the operations that can be performed on the data, not
how those operations are implemented.
• Data Structure Independence: ADTs can be implemented
using different data structures, such as arrays or linked lists,
without affecting the functionality of the ADT.
• Information Hiding: ADTs can protect the integrity of the
data by allowing access only to authorized users and
operations. This helps prevent errors and misuse of the
data.
Advantages:
• Encapsulation: ADTs provide a way to encapsulate data and
operations into a single unit, making it easier to manage and
modify the data structure.
• Abstraction: ADTs allow users to work with data structures
without having to know the implementation details, which
can simplify programming and reduce errors.
• Data Structure Independence: ADTs can be implemented
using different data structures, which can make it easier to
adapt to changing needs and requirements.
• Information Hiding: ADTs can protect the integrity of data
by controlling access and preventing unauthorized
modifications.
Disadvantages:
• Overhead: Implementing ADTs can add overhead in terms of
memory and processing, which can affect performance.
• Complexity: ADTs can be complex to implement, especially
for large and complex data structures.
• Learning Curve: Using ADTs requires knowledge of their
implementation and usage, which can take time and effort to
learn.
• Limited Flexibility: Some ADTs may be limited in their
functionality or may not be suitable for all types of data
structures.
• Cost: Implementing ADTs may require additional resources
and investment, which can increase the cost of
development.
ARRAY BASED IMPLEMENTATION:
What is an Array?
Array is a linear data structure that stores a collection of items of same
data type in contiguous memory locations. Each item in an array is
indexed starting with 0. We can directly access an array element by using
its index value.
Basic terminologies of Array
• Array Index: In an array, elements are identified by their indexes.
Array index starts from 0.
• Array element: Elements are items stored in an array and can
be accessed by their index.
• Array Length: The length of an array is determined by the
number of elements it can contain.
Declaration of Array
int arr[5];
char arr[10];
float arr[20];
Initialization of Array
int arr[] = { 1, 2, 3, 4, 5 };
char arr[5] = { 'a', 'b', 'c', 'd', 'e' };
float arr[10] = { 1.4, 2.0, 24, 5.0, 0.0 };
Types of Arrays
Arrays can be classified in two ways:
• On the basis of Memory Allocation
• On the basis of Dimensions
Types of Arrays on the basis of Memory Allocation:
1. Static Arrays:
In this type of array, memory is allocated at compile time having a fixed
size of it. We cannot alter or update the size of this array. This type
of memory allocation is also known as static or compile-time memory
allocation. Here only a fixed size (i.e. the size that is mentioned in
square brackets []) of memory will be allocated for storage. If we declare
a larger size and store a lesser number of elements will result in a
wastage of memory or we declare a lesser size than the number of
elements then we won't get enough memory to store all the elements. In
such cases, static memory allocation is not preferred.
Example:
int arr[5] = {1, 2, 3, 4, 5};
2. Dynamic Arrays:
In this type of array, memory is allocated at run time but not having a
fixed size. Suppose, a user wants to declare any random size of an
array, then we will not use a static array, instead of that a dynamic array
is used. This type of memory allocation is also known
as dynamic or run-time memory allocation. It is used to specify the size
of it during the run time of any program.
Example:
int* arr = new int[5];
Types of Arrays on the basis of Dimensions:
1. One-dimensional Array(1-D Array):
In One-dimensional array, the elements are stored one
after another.
2. Multi-dimensional Array:
A multi-dimensional array is an array with more than one dimension.
We can use multidimensional array to store complex data in the form
of tables, etc. We can have 2-D arrays, 3-D arrays, 4-D arrays and so
on.
• Two-Dimensional Array(2-D Array or Matrix):
2-D Multidimensional arrays can be considered as an array of
arrays or as a matrix consisting of rows and columns.
• Three-Dimensional Array(3-D Array):
A 3-D Multidimensional array contains three dimensions, so it
can be considered an array of two-dimensional arrays.
Operations on Array
1. Array Traversal:
Array traversal involves visiting all the elements of the array once.
int arr[] = { 1, 2, 3, 4, 5 };
int len = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < len; i++) {
cout << arr[i] << " ";
Output:
12345
2. Insertion in Array:
We can insert one or multiple elements at any position in the array.
void insertElement(int arr[], int n, int x, int pos)
{
for (int i = n - 1; i >= pos; i--)
arr[i + 1] = arr[i];
arr[pos] = x;
}
3.Deletion in Array:
We can delete an element at any index in an array.
int findElement(int arr[], int n, int key);
{
int i;
for (i = 0; i < n; i++)
if (arr[i] == key)
return i;
return -1;
}
int deleteElement(int arr[], int n, int key)
{
int pos = findElement(arr, n, key);
if (pos == -1) {
cout << "Element not found";
return n;
}
int i;
for (i = pos; i < n - 1; i++)
arr[i] = arr[i + 1];
return n - 1;
}
4.Searching in Array:
We can traverse over an array and search for an element.
int findElement(int arr[], int n, int key)
{
int i;
for (i = 0; i < n; i++)
if (arr[i] == key)
return i;
return -1;
}
Complexity Analysis of Operations on Array
Time Complexity:
Operation Best Case Average Case Worst Case
Traversal Ω(N) θ(N) O(N)
Insertion Ω(1) θ(N) O(N)
Deletion Ω(1) θ(N) O(N)
Searching Ω(1) θ(N) O(N)
Space Complexity:
Operation Best Case Average Case Worst Case
Traversal Ω(1) θ(1) O(1)
Insertion Ω(1) θ(N) O(N)
Operation Best Case Average Case Worst Case
Deletion Ω(1) θ(N) O(N)
Searching Ω(1) θ(1) O(1)
Advantages of Array
• Arrays allow random access to elements. This makes accessing
elements by position faster.
• Arrays have better cache locality which makes a pretty big
difference in performance.
• Arrays represent multiple data items of the same type using a
single name.
• Arrays are used to implement the other data structures like linked
lists, stacks, queues, trees, graphs, etc.
Disadvantages of Array
• As arrays have a fixed size, once the memory is allocated to
them, it cannot be increased or decreased, making it
impossible to store extra data if required. An array of fixed size is
referred to as a static array.
• Allocating less memory than required to an array leads to loss
of data.
• An array is homogeneous in nature so, a single array cannot
store values of different data types.
• Arrays store data in contiguous memory locations, which makes
deletion and insertion very difficult to implement.
Applications of Array
• They are used in the implementation of other data structures such
as array lists, heaps, hash tables, vectors, and matrices.
• Database records are usually implemented as arrays.
• It is used in lookup tables by computer.
LINKED LIST IMPLEMENTATION
What is a Linked List?
A linked list is a linear data structure that consists of a series of nodes
connected by pointers. Each node contains data and a reference to
the next node in the list. Unlike arrays, linked lists allow for
efficient insertion or removal of elements from any position in the list,
as the nodes are not stored contiguously in memory.
Linked List is a linear data structure which looks like a series of nodes,
where each node has two parts: data and next pointer. Unlike Arrays,
Linked List elements are not stored at a contiguous location. In the
linked list there is a head pointer, which points to the first element of the
linked list, and if the list is empty then it simply points to null or nothing.
Basic Terminologies of Linked List:
• Head: The Head of a linked list is a pointer to the first node or
reference of the first node of linked list. This pointer marks the
beginning of the linked list.
• Node: Linked List consists of a series of nodes where each node
has two parts: data and next pointer.
• Data: Data is the part of node which stores the information in the
linked list.
• Next pointer: Next pointer is the part of the node which points to
the next node of the linked list.
Importance of Linked List:
Here are a few advantages of a linked list that is listed below, it will help
you understand why it is necessary to know.
• Dynamic Data structure: The size of memory can be allocated
or de-allocated at run time based on the operation insertion or
deletion.
• Ease of Insertion/Deletion: The insertion and deletion of
elements are simpler than arrays since no elements need to be
shifted after insertion and deletion, Just the address needed to
be updated.
• Efficient Memory Utilization: As we know Linked List is a
dynamic data structure the size increases or decreases as per
the requirement so this avoids the wastage of memory.
• Implementation: Various advanced data structures can be
implemented using a linked list like a stack, queue, graph, hash
maps, etc.
Types of Linked List:
There are mainly three types of linked lists:
1. Singly linked list
2. Doubly linked list
3. Circular linked list
Advantages of Linked List:
• Dynamic nature: Linked lists are used for dynamic memory
allocation.
• Memory efficient: Memory consumption of a linked list is efficient
as its size can grow or shrink dynamically according to our
requirements, which means effective memory utilization hence,
no memory wastage.
• Ease of Insertion and Deletion: Insertion and deletion of nodes
are easily implemented in a linked list at any position.
• Implementation: For the implementation of stacks and queues
and for the representation of trees and graphs.
• The linked list can be expanded in constant time.
Disadvantages of Linked List:
• Memory usage: The use of pointers is more in linked lists hence,
complex and requires more memory.
• Accessing a node: Random access is not possible due to
dynamic memory allocation.
• Search operation costly: Searching for an element is costly and
requires O(n) time complexity.
• Traversing in reverse order: Traversing is more time-consuming
and reverse traversing is not possible in singly linked lists.
Applications of Linked List:
Here are some of the applications of a linked list:
• Linear data structures such as stack, queue, and non-linear data
structures such as hash maps, and graphs can be implemented
using linked lists.
• Dynamic memory allocation: We use a linked list of free blocks.
• Implementation of graphs: Adjacency list representation of
graphs is the most popular in that it uses linked lists to store
adjacent vertices.
• In web browsers and editors, doubly linked lists can be used to
build a forwards and backward navigation button.
•A circular doubly linked list can also be used for implementing data
structures like Fibonacci heaps.
SINGLY LINKED LIST:
Singly Linked List is a type of linked list where each node has two
parts: data and next pointer. The data part stores the information and
the next pointer points to the next node of the linked list. The next
pointer of the last node stores null as it is the last node of the linked list
and there is no next node. Traversal of items can be done in the
forward direction only due to the linking of every node to its next node.
Node Definition of Singly linked list:
class Node {
public:
int data;
Node* next;
};
Operations on Singly Linked List:
The following operations are performed on a Single Linked List
• Insertion: The insertion operation can be performed in three ways.
They are as follows..
Insertion in Linked List
Given a Linked List, the task is to insert a new node in this given Linked
List at the following positions:
• At the front of the linked list
• After a given node.
• At the end of the linked list.
How to Insert a Node at the Front/Beginning of Linked List
To insert a node at the start/beginning/front of a Linked List, we need to:
• Make the first node of Linked List linked to the new node
• Remove the head from the original first node of Linked List
• Make the new node as the Head of the Linked List.
How to Insert a Node after a Given Node in Linked List
To insert a node after a given node in a Linked List, we need to:
• Check if the given node exists or not.
o If it do not exists,
o terminate the process.
o If the given node exists,
o Make the element to be inserted
as a new node
o Change the next pointer of given
node to the new node
o Now shift the original next pointer
of given node to the next pointer
of new node
How to Insert a Node at the End of Linked List
To insert a node at the end of a Linked List, we need to:
• Go to the last node of the Linked List
• Change the next pointer of last node from NULL to the new
node
• Make the next pointer of new node as NULL to show the end of
Linked List
Deletion in Singly Linked List
Deleting a node from a singly linked list involves different steps
depending on whether the node to be deleted is the head node, a middle
node, or the last node in the list.
Steps to Delete a Node from a Singly Linked List:
1. Delete the Head Node:
o If the node to be deleted is the head node (the first node in
the list), the operation is straightforward.
o Steps:
1. Point the head of the list to the second node.
2. The first node is now effectively removed from the list
as there are no references to it.
2. Delete a Middle Node:
o If the node to be deleted is somewhere in the middle of the
list, you need to update the next pointer of the node
preceding the one to be deleted to point to the node following
the one to be deleted.
o Steps:
1. Traverse the list to find the node preceding the one to
be deleted.
2. Update the next pointer of the preceding node to point
to the node following the one to be deleted.
3. The node to be deleted is now effectively removed
from the list as there are no references to it.
3. Delete the Last Node:
o If the node to be deleted is the last node in the list, you need
to update the next pointer of the node preceding the last
node to null.
o Steps:
1. Traverse the list to find the node preceding the last
node.
2. Update the next pointer of this node to null.
3. The last node is now effectively removed from the list
as there are no references to it.
Search an element in a Linked List
To search for a node in a singly linked list, you need to traverse the list
from the head to the end, checking each node's data to see if it
matches the value you are searching for.
1. Start from the Head Node:
o Begin the search from the head (the first node) of the linked
list.
2. Traverse the List:
o Move from one node to the next using the next pointer.
3. Check Each Node's Data:
o Compare the data of the current node with the value you are
searching for.
o If the data matches, the node has been found, and you can
take the appropriate action (e.g., return the node, print a
message, etc.).
o If the data does not match, move to the next node.
4. End of List:
o Continue this process until you either find the node or reach
the end of the list (i.e., the next pointer is NULL).
5. Node Not Found:
o If you reach the end of the list without finding the node, the
value is not
Display:
This process displays the elements of a Single-linked list. Using
traversal would be helpful to visit each node in the linkedlist.
Example:
#include <iostream>
using namespace std;
class Node {
public:
int data;
Node* next;
};
void push(Node** head_ref, int new_data)
Node* new_node = new Node();
new_node->data = new_data;
new_node->next = (*head_ref);
(*head_ref) = new_node;
void deleteNode(Node** head_ref, int position)
if (*head_ref == NULL)
return;
Node* temp = *head_ref;
if (position == 0) {
*head_ref = temp->next;
free(temp);
return;
for (int i = 0; temp != NULL && i < position - 1; i++)
temp = temp->next;
if (temp == NULL || temp->next == NULL)
return;
Node* next = temp->next->next;
free(temp->next); // Free memory
temp->next = next;
}
void printList(Node* node)
while (node != NULL) {
cout << node->data << " ";
node = node->next;
int main()
Node* head = NULL;
push(&head, 7);
push(&head, 1);
push(&head, 3);
push(&head, 2);
push(&head, 8);
cout << "Created Linked List: ";
printList(head);
deleteNode(&head, 4);
cout << "\nLinked List after Deletion at position 4: ";
printList(head);
return 0;
Output:
Created Linked List: 8 2 3 1 7
Linked List after Deletion at position 4: 8 2 3 1
Time Complexity:
• Best Case: O(1) if given position is 1
• Average & Worst Case: O(N) where N is the length of the linked
list. This is because in the worst case, we need to traverse the
entire linked list to find the node to be deleted.
Auxiliary Space: O(1) as we are only using a constant amount of extra
space for temporary variables and pointers, regardless of the size of the
linked list. Specifically, we only need to allocate memory for a few
temporary pointers (e.g., current, temp) and the new head pointer if the
original head is deleted. The memory for the deleted node(s) is also
freed as soon as it is deleted
Advantages of Singly Linked Lists:
1. Simple Implementation: Easier to implement compared to other
complex data structures like trees and graphs.
2. Memory Efficiency: Requires less memory per node compared to
doubly linked lists due to the absence of a previous pointer.
3. Dynamic Size: Can easily grow or shrink in size by adjusting
pointers, facilitating dynamic memory management.
4. Efficient Insertion and Deletion: Insertion and deletion at the
beginning and middle of the list are efficient with O(1) time
complexity, given access to the predecessor node.
Disadvantages of Singly Linked Lists:
1. Limited Traversal: Only supports forward traversal, which can be
restrictive for certain operations.
2. No Backtracking: Lack of a previous pointer makes backtracking
or traversal in reverse direction difficult and inefficient.
3. Memory Overhead: Each node requires additional memory space
for storing a pointer to the next node.
4. Access Complexity: Direct access to individual nodes requires
traversal from the head, which can be inefficient for certain
operations.
Applications of Singly Linked Lists:
1. Stacks and Queues: Efficiently implement dynamic data
structures like stacks and queues.
2. Dynamic Memory Management: Facilitate dynamic memory
allocation and deallocation in languages without built-in garbage
collection.
3. Task Scheduling: Manage tasks or processes in round-robin
scheduling algorithms.
4. Sensor Data Processing: Handle real-time sensor data streams
where data is processed sequentially.
DOUBLY LINKED LIST:
Doubly Linked List is a type of linked list where each node has three
parts: data, next pointer and previous pointer. The data part stores
the information, the next pointer points to the next node of the linked
list and the previous pointer points to the previous node of the linked
list. The next pointer of the last node and the previous pointer of the
first node stores NULL. Traversal of items can be done in the forward
direction as well as backward direction due to the linking of every
node to its next node as well as the previous node.
Node Definition of Doubly Linked List:
class Node {
public:
int data;
Node* next;
Node* prev;
};
Insertion in a Doubly Linked List
Inserting a new node in a doubly linked list is very similar to inserting
new node in linked list. There is a little extra work required to maintain
the link of the previous node. A node can be inserted in a Doubly
Linked List in four ways:
• At the front of the DLL.
• In between two nodes
o After a given node.
o Before a given node.
• At the end of the DLL.
Insertion at the Beginning in Doubly Linked List:
To insert a new node at the beginning of the doubly list, we can use the
following steps:
• Allocate memory for a new node (say new_node) and assign
the provided value to its data field.
• Set the previous pointer of the new_node to nullptr.
• If the list is empty:
o Set the next pointer of the new_node to nullptr.
o Update the head pointer to point to
the new_node.
• If the list is not empty:
o Set the next pointer of the new_node to the
current head.
o Update the previous pointer of the current head
to point to the new_node.
o Update the head pointer to point to
the new_node.
Insertion in between two nodes in Doubly Linked
List:
1. Add a node after a given node in a Doubly Linked List:
We are given a pointer to a node as prev_node, and the new node is
inserted after the given node. This can be done using the following
steps:
• Firstly create a new node (say new_node).
• Now insert the data in the new node.
• Point the next of new_node to the next of prev_node.
• Point the next of prev_node to new_node.
• Point the previous of new_node to prev_node.
2. Add a node before a given node in a Doubly Linked List:
Let the pointer to this given node be next_node. This can be done
using the following steps.
• Allocate memory for the new node, let it be called new_node.
• Put the data in new_node.
• Set the previous pointer of this new_node as the previous
node of the next_node.
• Set the previous pointer of the next_node as the new_node.
• Set the next pointer of this new_node as the next_node.
• Set the next pointer of the previous
of new_node to new_node.
Insertion at the End in Doubly Linked List:
The new node is always added after the last node of the given Linked
List. This can be done using the following steps:
• Create a new node (say new_node).
• Put the value in the new node.
• Make the next pointer of new_node as null.
• If the list is empty, make new_node as the head.
• Otherwise, travel to the end of the linked list.
• Now make the next pointer of last node point to new_node.
• Change the previous pointer of new_node to the last node of
the list.
Deletion in Doubly linked list
Deleting a node from a doubly linked list involves different steps
compared to a singly linked list because each node has two pointers:
one to the next node and one to the previous node. Here’s a detailed
explanation:
Steps to Delete a Node from a Doubly Linked List
1. Delete the Head Node:
o If the node to be deleted is the head node (the first node in
the list), update the head pointer to point to the second node,
and update the previous pointer of the new head node to be
NULL.
o Steps:
1. Check if the list is empty. If it is, there's nothing to
delete.
2. If the node to be deleted is the head node:
▪ Update the head pointer to the second node
(head = head->next).
▪ If the new head is not NULL, set its previous
pointer to NULL (head->prev = NULL).
▪ Free the old head node.
2. Delete a Middle Node:
o If the node to be deleted is somewhere in the middle of the
list, adjust the next pointer of the previous node and the prev
pointer of the next node to bypass the node to be deleted.
o Steps:
1. Traverse the list to find the node to be deleted.
2. Update the next pointer of the previous node to point to
the node following the one to be deleted (prev->next =
node->next).
3. Update the prev pointer of the next node to point to the
node preceding the one to be deleted (next->prev =
node->prev).
4. Free the node to be deleted.
3. Delete the Last Node:
o If the node to be deleted is the last node, update the next
pointer of the previous node to be NULL.
o Steps:
1. Traverse the list to find the node to be deleted.
2. Update the next pointer of the previous node to NULL
(prev->next = NULL).
3. Free the last node.
Display:
This process displays the elements of a double-linked list. Using
traversal would be helpful to visit each node in the list.
Example:
#include <iostream>
using namespace std;
class Node {
public:
int data;
Node* next;
Node* prev;
};
void deleteNode(Node** head_ref, Node* del) {
if (*head_ref == NULL || del == NULL)
return;
if (*head_ref == del)
*head_ref = del->next;
if (del->next != NULL)
del->next->prev = del->prev;
if (del->prev != NULL)
del->prev->next = del->next;
delete del;
return;
void push(Node** head_ref, int new_data) {
Node* new_node = new Node();
new_node->data = new_data;
new_node->next = (*head_ref);
new_node->prev = NULL;
if ((*head_ref) != NULL)
(*head_ref)->prev = new_node;
(*head_ref) = new_node;
void printList(Node* node) {
while (node != NULL) {
cout << node->data << " ";
node = node->next;
int main() {
Node* head = NULL;
push(&head, 2);
push(&head, 4);
push(&head, 8);
push(&head, 10);
cout << "Created DLL is: ";
printList(head);
deleteNode(&head, head);
cout << "\nDLL after deletion of first node: ";
printList(head);
deleteNode(&head, head->next);
cout << "\nDLL after deletion of middle node: ";
printList(head);
return 0;
Output:
Created DLL is: 10 8 4 2
DLL after deletion of first node: 8 4 2
DLL after deletion of middle node: 8 2
Advantages of Doubly Linked Lists:
1. Bidirectional Traversal: Nodes can be traversed in both forward
and backward directions, enhancing flexibility.
2. Insertion and Deletion Efficiency: Allows for efficient insertion
and deletion operations at both ends and in the middle of the list.
3. Memory Efficiency: Offers better memory utilization compared to
circular linked lists for certain applications.
4. Versatility in Applications: Suitable for implementing advanced
data structures like stacks, queues, and associative arrays.
Disadvantages of Doubly Linked Lists:
1. Higher Memory Overhead: Requires extra space to store the
previous pointer, increasing memory consumption.
2. Complexity: More complex to implement and maintain compared
to singly linked lists.
3. Synchronization Issues: Concurrent modifications can lead to
synchronization issues if not handled carefully.
4. Overhead in Operations: Some operations, such as traversal and
searching, may require more operations compared to singly linked
lists.
Applications of Doubly Linked Lists:
1. Dynamic Data Structures: Manage data structures requiring
frequent insertions, deletions, and updates.
2. Text Editors: Efficiently manage undo and redo operations with
bidirectional navigation.
3. LRU Caches: Implement Least Recently Used (LRU) caching
policies efficiently.
4. Music Playlist Management: Enable backward and forward
navigation through playlists in music applications.
CIRCULAR LINKED LIST
What is Circular linked list?
The circular linked list is a linked list where all nodes are connected to
form a circle. In a circular linked list, the first node and the last node
are connected to each other which forms a circle. There is no NULL at
the end.
There are generally two types of circular linked lists:
• Circular singly linked list:
In a circular Singly linked list, the last node of the list contains a
pointer to the first node of the list. We traverse the circular singly
linked list until we reach the same node where we started. The
circular singly linked list has no beginning or end. No null value is
present in the next part of any of the nodes.
• Circular Doubly linked list:
Circular Doubly Linked List has properties of both doubly linked list
and circular linked list in which two consecutive elements are
linked or connected by the previous and next pointer and the last
node points to the first node by the next pointer and also the first
node points to the last node by the previous pointer.
Node representation of a Circular Linked List:
class Node{
int value;
Node next;
}
Insertion in a Circular Linked List
Insertion in a circular linked list can occur at the beginning, end, or a
specified position.
Insertion at the Beginning
1. Create a New Node:
o Allocate memory for the new node and set its data.
2. Check if the List is Empty:
o If the list is empty, point the new node to itself.
3. Traverse to the Last Node:
o If the list is not empty, traverse to the last node (the node
whose next pointer points to the head).
4. Insert the New Node:
o Set the new node's next pointer to the current head.
o Update the last node's next pointer to point to the new node.
o Update the head to point to the new node.
Insertion at the End
1. Create a New Node:
o Allocate memory for the new node and set its data.
2. Check if the List is Empty:
o If the list is empty, point the new node to itself and update the
head.
3. Traverse to the Last Node:
o If the list is not empty, traverse to the last node (the node
whose next pointer points to the head).
4. Insert the New Node:
o Set the last node's next pointer to the new node.
o Set the new node's next pointer to the head.
Example:
#include <iostream>
using namespace std;
class Node {
public:
int data;
Node* next;
};
void insertAtBeginning(Node** head_ref, int new_data) {
Node* new_node = new Node();
new_node->data = new_data;
if (*head_ref == NULL) {
new_node->next = new_node;
*head_ref = new_node;
} else {
Node* temp = *head_ref;
while (temp->next != *head_ref)
temp = temp->next;
new_node->next = *head_ref;
temp->next = new_node;
*head_ref = new_node;
void insertAtEnd(Node** head_ref, int new_data) {
Node* new_node = new Node();
new_node->data = new_data;
if (*head_ref == NULL) {
new_node->next = new_node;
*head_ref = new_node;
} else {
Node* temp = *head_ref;
while (temp->next != *head_ref)
temp = temp->next;
temp->next = new_node;
new_node->next = *head_ref;
void printList(Node* head) {
if (head == NULL)
return;
Node* temp = head;
do {
cout << temp->data << " ";
temp = temp->next;
} while (temp != head);
cout << endl;
int main() {
Node* head = NULL;
insertAtEnd(&head, 5);
insertAtEnd(&head, 10);
insertAtBeginning(&head, 1);
insertAtBeginning(&head, 0);
cout << "Circular Linked List: ";
printList(head);
return 0;
Output:
Circular Linked List: 0 1 5 10
Deletion in a Circular Linked List
Deletion in a circular linked list can also occur at the beginning, end, or a
specified position.
Deletion of the Head Node
1. Check if the List is Empty:
o If the list is empty, there's nothing to delete.
2. Find the Last Node:
o Traverse to the last node (the node whose next pointer
points to the head).
3. Update the Pointers:
o Set the last node's next pointer to the head's next node.
o Update the head to point to the head's next node.
o Free the old head node.
Deletion of the Last Node
1. Check if the List is Empty:
o If the list is empty, there's nothing to delete.
2. Traverse to the Second Last Node:
o Traverse to the node just before the last node.
3. Update the Pointers:
o Set the second last node's next pointer to the head.
o Free the old last node.
Deletion at a Specified Position
1. Check if the List is Empty:
o If the list is empty, there's nothing to delete.
2. Traverse to the Node to be Deleted:
o Traverse to the node just before the one to be deleted.
3. Update the Pointers:
o Set the previous node's next pointer to the node after the
one to be deleted.
o Free the node to be deleted.
Example:
#include <bits/stdc++.h>
using namespace std;
class Node {
public:
int data;
Node* next;
};
void push(Node** head_ref, int data)
Node* ptr1 = new Node();
ptr1->data = data;
ptr1->next = *head_ref;
if (*head_ref != NULL) {
Node* temp = *head_ref;
while (temp->next != *head_ref)
temp = temp->next;
temp->next = ptr1;
else
ptr1->next = ptr1;
*head_ref = ptr1;
void printList(Node* head)
Node* temp = head;
if (head != NULL) {
do {
cout << temp->data << " ";
temp = temp->next;
} while (temp != head);
}
cout << endl;
void deleteNode(Node** head, int key)
if (*head == NULL)
return;
if ((*head)->data == key && (*head)->next == *head) {
free(*head);
*head = NULL;
return;
Node *last = *head, *d;
if ((*head)->data == key) {
while (last->next != *head)
last = last->next;
last->next = (*head)->next;
free(*head);
*head = last->next;
return;
while (last->next != *head && last->next->data != key) {
last = last->next;
if (last->next->data == key) {
d = last->next;
last->next = d->next;
free(d);
else
cout << "Given node is not found in the list!!!\n";
int main()
Node* head = NULL;
push(&head, 2);
push(&head, 5);
push(&head, 7);
push(&head, 8);
push(&head, 10);
cout << "List Before Deletion: ";
printList(head);
deleteNode(&head, 7);
cout << "List After Deletion: ";
printList(head);
return 0;
Output:
List Before Deletion: 10 8 7 5 2
List After Deletion: 10 8 5 2
Time Complexity: O(N), Worst case occurs when the element to be
deleted is the last element and we need to move through the whole list.
Auxiliary Space: O(1), As constant extra space is used.
Advantages of Circular Linked Lists:
1. Flexible Starting Point: Any node can initiate traversal, offering
flexibility.
2. Queue Implementation: Efficient for queues as it supports
constant-time enqueue and dequeue operations.
3. Cyclic Processing: Ideal for applications requiring cyclical
processing, like round-robin scheduling.
4. Memory Efficiency: Simplifies resource allocation and
management in certain scenarios.
Disadvantages of Circular Linked Lists:
1. Complexity: More intricate to implement and manage compared
to linear structures.
2. Reversal Complexity: Reversing the list is more challenging than
in linear linked lists.
3. Infinite Loops: Careless handling can lead to unintended infinite
loops during traversal.
4. Access Complexity: Lack of direct access to individual nodes
makes random access operations cumbersome.
Applications of Circular Linked Lists:
1. Multiplayer Games: Rotate player turns in a circular manner.
2. Operating Systems: Manage running applications using a round-
robin scheduling approach.
3. Circular Buffers: Implement efficient data structures for buffering
and queuing.
4. Resource Allocation: Efficiently manage and allocate resources
in embedded systems and simulations.
Linked List vs. Array:
Array Linked List
Arrays are stored in contiguous Linked Lists are not stored in contiguous
location. location.
Fixed size. Dynamic Size
Memory is allocated at compile
Memory is allocated at run time.
time.
Uses less memory than Linked Uses more memory than Arrays as it stores
Lists. both data and address of next node.
Elements can be accessed Elements can be access by traversing through
easily in O(1) time. all the nodes till we reach the required node.
Insertion and deletion operation Insertion and deletion operation is faster than
is slower than Linked List. Linked List.
Time Complexity Analysis of Linked List and Array:
Operation Linked list Array
Random Access O(N) O(1)
Insertion and deletion at beginning O(1) O(N)
Insertion and deletion at end O(N) O(1)
Insertion and deletion at a random position O(N) O(N)
POLYNOMIAL MANIPULATION:
Polynomial manipulation in data structures and algorithms (DSA)
involves various operations on polynomials, which are mathematical
expressions comprising terms with coefficients and exponents.
What are Polynomials?
A polynomial is a mathematical expression that involves variables
(like x), numbers, and operations like addition, subtraction, and
multiplication and the power of variables is non-negative integers.
Explanation of Polynomial Manipulation:
1. Representation:
o Array Representation: Polynomials can be represented
using arrays where each index represents the degree of the
term, and the value at that index represents the coefficient.
For example, poly[2] = 5 represents 5x^2.
o Linked List Representation: Alternatively, polynomials
can be represented using linked lists where each node
represents a term with a coefficient and an exponent. Each
node points to the next term in the polynomial.
2. Operations:
o Insertion: Adding a term to the polynomial involves finding
the correct position based on the exponent and inserting or
updating the coefficient.
o Deletion: Removing a term from the polynomial involves
finding the term with the specified exponent and deleting it
from the polynomial.
o Merge (Addition): Merging two polynomials (addition)
involves combining like terms (terms with the same
exponent) by adding their coefficients.
o Traversal: Traversing a polynomial involves iterating
through all terms to perform operations like printing,
evaluating, or processing each term.
o Multiplication: Multiplying polynomials involves
distributing each term in one polynomial across all terms in
the other polynomial and then combining like terms.
o Evaluation: Evaluating a polynomial at a given value of x
involves substituting the value of x into the polynomial
equation and calculating the result.
o Differentiation and Integration:
▪ Differentiation: Finding the derivative of a
polynomial involves reducing the exponent of each
term by one and multiplying by the original coefficient.
▪ Integration: Finding the integral of a polynomial
involves increasing the exponent of each term by one
and dividing the coefficient by the new exponent.
3. Applications:
o Computer Graphics: Used in rendering curves and
surfaces.
o Signal Processing: Modelling signals using polynomials.
o Mathematical Modelling: Representing natural
phenomena with polynomial equations.
o Error Detection and Correction: In coding theory,
polynomials are used for error detection and correction
codes.
Example:
#include <iostream>
using namespace std;
class Node {
public:
int coefficient;
int exponent;
Node* next;
Node(int coeff, int exp) : coefficient(coeff), exponent(exp), next(nullptr) {}
};
void insertTerm(Node** head, int coeff, int exp) {
Node* newTerm = new Node(coeff, exp);
if (*head == nullptr || exp > (*head)->exponent) {
newTerm->next = *head;
*head = newTerm;
} else {
Node* current = *head;
while (current->next != nullptr && current->next->exponent > exp) {
current = current->next;
newTerm->next = current->next;
current->next = newTerm;
void deleteTerm(Node** head, int exp) {
if (*head == nullptr) return;
Node* temp = *head;
if (temp->exponent == exp) {
*head = temp->next;
delete temp;
return;
Node* prev = nullptr;
while (temp != nullptr && temp->exponent != exp) {
prev = temp;
temp = temp->next;
}
if (temp == nullptr) return;
prev->next = temp->next;
delete temp;
Node* mergePolynomials(Node* poly1, Node* poly2) {
Node* result = nullptr;
Node* current = result;
while (poly1 != nullptr && poly2 != nullptr) {
if (poly1->exponent > poly2->exponent) {
insertTerm(&result, poly1->coefficient, poly1->exponent);
poly1 = poly1->next;
} else if (poly1->exponent < poly2->exponent) {
insertTerm(&result, poly2->coefficient, poly2->exponent);
poly2 = poly2->next;
} else {
int sum = poly1->coefficient + poly2->coefficient;
insertTerm(&result, sum, poly1->exponent);
poly1 = poly1->next;
poly2 = poly2->next;
while (poly1 != nullptr) {
insertTerm(&result, poly1->coefficient, poly1->exponent);
poly1 = poly1->next;
}
while (poly2 != nullptr) {
insertTerm(&result, poly2->coefficient, poly2->exponent);
poly2 = poly2->next;
return result;
void printPolynomial(Node* head) {
if (head == nullptr) {
cout << "Empty polynomial." << endl;
return;
Node* current = head;
while (current != nullptr) {
cout << current->coefficient << "x^" << current->exponent;
if (current->next != nullptr) {
cout << " + ";
current = current->next;
cout << endl;
int main() {
Node* poly1 = nullptr;
Node* poly2 = nullptr;
insertTerm(&poly1, 5, 2);
insertTerm(&poly1, -3, 1);
insertTerm(&poly1, 2, 0);
insertTerm(&poly2, 4, 3);
insertTerm(&poly2, 2, 1);
cout << "Polynomial 1: ";
printPolynomial(poly1);
cout << "Polynomial 2: ";
printPolynomial(poly2);
Node* result = mergePolynomials(poly1, poly2);
cout << "Result of addition: ";
printPolynomial(result);
deleteTerm(&poly1, 1);
cout << "Polynomial 1 after deletion of x^1 term: ";
printPolynomial(poly1);
return 0;
Output:
Polynomial 1: 5x^2 + -3x^1 + 2x^0
Polynomial 2: 4x^3 + 2x^1
Result of addition: 4x^3 + 5x^2 + -1x^1 + 2x^0
Polynomial 1 after deletion of x^1 term: 5x^2 + 2x^0