0% found this document useful (0 votes)
16 views60 pages

DS Unit-Ii

The document provides an overview of linked lists, including singly linked lists, doubly linked lists, and their operations such as insertion, deletion, and traversal. It highlights the advantages of linked lists over arrays, such as dynamic memory allocation and ease of insertion/deletion. Additionally, it covers the structure and implementation of nodes in both singly and doubly linked lists, along with various operations applicable to each type.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
16 views60 pages

DS Unit-Ii

The document provides an overview of linked lists, including singly linked lists, doubly linked lists, and their operations such as insertion, deletion, and traversal. It highlights the advantages of linked lists over arrays, such as dynamic memory allocation and ease of insertion/deletion. Additionally, it covers the structure and implementation of nodes in both singly and doubly linked lists, along with various operations applicable to each type.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 60

UNIT – II

LINKED LISTS:

 SINGLY LINKED LISTS: REPRESENTATION AND OPERATIONS


 DOUBLY LINKED LISTS
 CIRCULAR LINKED LISTS
 COMPARING ARRAYS AND LINKED LISTS
 APPLICATIONS OF LINKED LISTS
STACKS:
 INTRODUCTION TO STACKS: PROPERTIES AND OPERATIONS
 IMPLEMENTATION OF STACK USING ARRAYS
 IMPLEMENTATION OF STACK USING LINKED LISTS
LINKED LISTS
Linked List is basically chains of nodes where each node contains information such as
data and a pointer to the next node in the chain. It is a popular data structure with a
wide range of real-world applications. 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.
SINGLY LINKED LIST
A singly linked list is a fundamental data structure, it consists of nodes where each node
contains a data field and a reference to the next node in the linked list. The next of the
last node is null, indicating the end of the list. Linked Lists support efficient insertion and
deletion operations.

Understanding Node Structure


In a singly linked list, each node consists of two parts: data and a pointer to the next node.
This structure allows nodes to be dynamically linked together, forming a chain-like
sequence.

Definition of a Node in a singly linked list:


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

Function to create a new Node:


struct Node* newNode(int data) {
struct Node* temp =
(struct Node*)malloc(sizeof(struct Node));
temp->data = data;
temp->next = NULL;
return temp;
}
In this example, the Node class contains an integer data field (data) to store the
information and a pointer to another Node (next) to establish the link to the next node in
the list.
1. Traversal of Singly Linked List
Traversal in a linked list means visiting each node and performing operations like printing
or processing data.
Step-by-step approach:
1. Initialize a pointer (current) to the head of the list.
2. Loop through the list using a while loop until current becomes NULL.
3. Process each node (e.g., print its data).
4. Move to the next node by updating current = current->next.
2. Searching in Singly Linked List
Searching in a Singly Linked List refers to the process of looking for a specific element or
value within the elements of the linked list.
Step-by-step approach:
1. Start from the head of the linked list.
2. Check each node’s data:
o If it matches the target value, return true (element found).
o Otherwise, move to the next node.
3. Repeat until the end (NULL) is reached.
4. If no match is found, return false.
3. Length of Singly Linked List
Finding the length of a Singly Linked List means counting the total number of nodes.
Step-by-step approach:
1. Initialize a counter (length = 0).
2. Start from the head, assign it to current.
3. Traverse the list:
o Increment length for each node.
o Move to the next node (current = current->next).
4. Return the final length when current becomes NULL.
4. Insertion in Singly Linked List
Insertion is a fundamental operation in linked lists that involves adding a new node to the
list. There are several scenarios for insertion:
a. Insertion at the Beginning of Singly Linked List: Insertion at the beginning involves
adding a new node before the current head, making it the new head.
Step-by-step approach:
 Create a new node with the given value.
 Set the next pointer of the new node to the current head.
 Move the head to point to the new node.
 Return the new head of the linked list.
b. Insertion at the End of Singly Linked List: To insert a node at the end of the list,
traverse the list until the last node is reached, and then link the new node to the current
last node

Step-by-step approach:
 Create a new node with the given value.
 Check if the list is empty:
o If it is, make the new node the head and return.
 Traverse the list until the last node is reached.
 Link the new node to the current last node by setting the last node's next pointer to
the new node.
c. Insertion at a Specific Position of the Singly Linked List: To insert a node at a
specific position, traverse the list to the desired position, link the new node to the next
node, and update the links accordingly.
Step-by-step approach:
 Create a new node and assign it a value.
 If inserting at the beginning (position = 1):
o Point the new node’s next to the current head.
o Update the head to the new node.
o Return (Insertion done).
 Otherwise, traverse the list:
o Start from the head and move to the (position - 1)ᵗʰ node (just before the
desired position).
o If the position is beyond the list length, return an error or append at the end.
 Insert the new node:
o Point the new node’s next to the next node of the current position.
o Update the previous node’s next to the new node.
 Return the updated list.
5. Deletion in Singly Linked List
Deletion involves removing a node from the linked list. Similar to insertion, there are
different scenarios for deletion:
a. Deletion at the Beginning of Singly Linked List: To delete the first node, update the
head to point to the second node in the list.

Steps-by-step approach:
 Check if the head is NULL.
o If it is, return NULL (the list is empty).
 Store the current head node in a temporary variable temp.
 Move the head pointer to the next node.
 Delete the temporary node.
 Return the new head of the linked list.
b. Deletion at the End of Singly Linked List: To delete the last node, traverse the list
until the second-to-last node and update its next field to None.

Step-by-step approach:
 Check if the head is NULL.
o If it is, return NULL (the list is empty).
 Check if the head's next is NULL (only one node in the list).
o If true, delete the head and return NULL.
 Traverse the list to find the second last node (second_last).
 Delete the last node (the node after second_last).
 Set the next pointer of the second last node to NULL.
 Return the head of the linked list.
c. Deletion at a Specific Position of Singly Linked List: To delete a node at a specific
position, traverse the list to the desired position, update the links to bypass the node to be
deleted.

Step-by-step approach:
 Check if the list is empty or the position is invalid, return if so.
 If the head needs to be deleted, update the head and delete the node.
 Traverse to the node before the position to be deleted.
 If the position is out of range, return.
 Store the node to be deleted.
 Update the links to bypass the node.
 Delete the stored node.
6. Modify a Singly Linked List
Updating in a Singly Linked List means modifying the value of a node at a given position.
Step-by-step approach:
 Start from the head of the list.
 Traverse to the required position (move current node to position).
 Check if the position is valid:
o If the position is out of bounds, return an error.
 Update the node’s data with the new value.
 Return the modified list.
7. Reversing a Singly Linked List
Reversing a singly linked list means changing the direction of pointers so that the last node
becomes the new head.
Step-by-step approach:
 Initialize three pointers:
o prev = NULL (to track the previous node)
o current = head (starting point)
o next = NULL (to store the next node temporarily)
 Iterate through the list:
o Store next = current->next (save next node).
o Reverse the link: current->next = prev.
o Move prev and current forward (prev = current, current = next).
 Update head to prev (new head is the last node).
DUBLY LINKED LIST
A doubly linked list is a more complex data structure than a singly linked list, but it offers
several advantages. The main advantage of a doubly linked list is that it allows for efficient
traversal of the list in both directions. This is because each node in the list contains a
pointer to the previous node and a pointer to the next node. This allows for quick and easy
insertion and deletion of nodes from the list, as well as efficient traversal of the list in both
directions.
Representation of Doubly Linked List in Data Structure
In a data structure, a doubly linked list is represented using nodes that have three fields:
1. Data
2. A pointer to the next node (next)
3. A pointer to the previous node (prev)

Node Definition
Here is how a node in a Doubly Linked List is typically represented:
struct Node {

// To store the Value or data.


int data;

// Pointer to point the Previous Element


Node* prev;

// Pointer to point the Next Element


Node* next;
};

// Function to create a new node


struct Node *createNode(int new_data) {
struct Node *new_node = (struct Node *)
malloc(sizeof(struct Node));
new_node->data = new_data;
new_node->next = NULL;
new_node->prev = NULL;
return new_node;
}
Each node in a Doubly Linked List contains the data it holds, a pointer to the next node
in the list, and a pointer to the previous node in the list. By linking these nodes together
through the next and prev pointers, we can traverse the list in both directions (forward
and backward), which is a key feature of a Doubly Linked List.
Operations:
 Traversal in Doubly Linked List
 Finding Length of Doubly Linked List
 Insertion in a Doubly Linked List
 Deletion in a Doubly Linked List
 Advantages of Doubly Linked List
 Disadvantages of Doubly Linked List
 Applications of Doubly Linked List
1. Traversal in Doubly Linked List
Traversal in a Doubly Linked List involves visiting each node, processing its data, and
moving to the next or previous node using the forward (next) and backward (prev) pointers.
Step-by-Step Approach for Traversal:
1. Start from the head of the list.
2. Traverse forward:
o Visit the current node and process its data (e.g., print it).
o Move to the next node using current = current->next.
o Repeat the process until the end of the list (current == NULL).
3. Optionally, traverse backward:
o Start from the tail (last node).
o Visit the current node and process its data.
o Move to the previous node using current = current->prev.
o Repeat the process until the beginning of the list (current == NULL).
Traversal is useful for displaying or processing all nodes in a doubly linked list.
2. Finding Length of Doubly Linked List
A Doubly Linked List (DLL) is a type of linked list where each node has two pointers:
1. One pointing to the next node in the sequence.
2. One pointing to the previous node in the sequence.
To find the length of a doubly linked list, we need to traverse the list while counting the
nodes.
Step-by-Step Approach for finding length:
1. Initialize a counter: Start with a counter variable (count = 0).
2. Set a pointer to the head node: Use a pointer (current) and initialize it to the head
of the linked list.
3. Traverse the list:
o While the pointer (current) is not NULL, increment the count by 1.
o Move to the next node (current = current.next).
4. Stop at the end of the list: When the pointer reaches NULL, stop the loop.
5. Return the count: The final value of count gives the length of the doubly linked list.
3. Insertion in a Doubly Linked List
Insertion in a Doubly Linked List (DLL) involves adding a new node at a specific position
while maintaining the connections between nodes. Since each node contains a pointer to
both the previous and next node, insertion requires adjusting these pointers carefully.
There are three primary types of insertion in a DLL:
1. Insertion at the Beginning
1. Create a new node with the given data.
2. Set the next pointer of the new node to the current head.
3. If the list is not empty, update the prev pointer of the current head to point to the
new node.
4. Update the head of the list to the new node.
2. Insertion at the End
1. Create a new node with the given data.
2. If the list is empty, set the new node as the head.
3. Traverse the list until the last node is found.
4. Set the next pointer of the last node to the new node.
5. Set the prev pointer of the new node to the last node.
3. Insertion at a Specific Position
1. Create a new node with the given data.
2. If inserting at the beginning, follow the steps for insertion at the start.
3. Traverse the list to find the node after which insertion is needed.
4. Set the next pointer of the new node to the next node of the current position.
5. Set the prev pointer of the new node to the current node.
6. Update the prev pointer of the next node to point to the new node (if it exists).
7. Update the next pointer of the previous node to point to the new node.
4. Deletion in a Doubly Linked List
Deletion in a Doubly Linked List (DLL) involves removing a node while maintaining the
integrity of the list. Since each node contains pointers to both its previous and next nodes,
deletion requires careful pointer adjustments to ensure no broken links occur.
Types of Deletion in a Doubly Linked List
1. Deletion at the Beginning
1. Check if the list is empty; if it is, return as there is nothing to delete.
2. Store the current head node in a temporary variable.
3. Move the head pointer to the next node.
4. If the new head exists, update its prev pointer to NULL.
5. Delete the old head node to free memory.
2. Deletion at the End
1. Check if the list is empty; if it is, return.
2. Traverse the list to find the last node.
3. Store the last node in a temporary variable.
4. Update the next pointer of the second-last node to NULL, making it the new tail.
5. Delete the last node to free memory.
3. Deletion at a Specific Position
1. Check if the list is empty; if it is, return.
2. Traverse the list to find the node to be deleted.
3. Store the node to be deleted in a temporary variable.
4. Update the next pointer of the previous node to point to the next node.
5. Update the prev pointer of the next node to point to the previous node (if it exists).
6. Delete the target node to free memory.
Advantages of Doubly Linked List
 Efficient traversal in both directions: Doubly linked lists allow for efficient
traversal of the list in both directions, making it suitable for applications where
frequent insertions and deletions are required.
 Easy insertion and deletion of nodes: The presence of pointers to both the previous
and next nodes makes it easy to insert or delete nodes from the list, without having
to traverse the entire list.
 Can be used to implement a stack or queue: Doubly linked lists can be used to
implement both stacks and queues, which are common data structures used in
programming.
Disadvantages of Doubly Linked List
 More complex than singly linked lists: Doubly linked lists are more complex than
singly linked lists, as they require additional pointers for each node.
 More memory overhead: Doubly linked lists require more memory overhead than
singly linked lists, as each node stores two pointers instead of one.
Applications of Doubly Linked List
 Implementation of undo and redo functionality in text editors.
 Cache implementation where quick insertion and deletion of elements are required.
 Browser history management to navigate back and forth between visited pages.
 Music player applications to manage playlists and navigate through songs efficiently.
 Implementing data structures like Deque (double-ended queue) for efficient insertion
and deletion at both ends.
CIRCULAR LINKED LIST
A circular linked list is a data structure where the last node connects back to the first,
forming a loop. This structure allows for continuous traversal without any interruptions.
Circular linked lists are especially helpful for tasks like scheduling and managing playlists,
allowing for smooth navigation. In this tutorial, we’ll cover the basics of circular linked
lists, how to work with them, their advantages and disadvantages, and their applications.
What is a Circular Linked List?
A circular linked list is a special type of linked list where all the nodes are connected to
form a circle. Unlike a regular linked list, which ends with a node pointing to NULL, the
last node in a circular linked list points back to the first node. This means that you can
keep traversing the list without ever reaching a NULL value.
Types of Circular Linked Lists
We can create a circular linked list from both singly linked lists and doubly linked lists. So,
circular linked lists are basically of two types:
1. Circular Singly Linked List
In Circular Singly Linked List, each node has just one pointer called the “next” pointer. The
next pointer of the last node points back to the first node and this results in forming a
circle. In this type of Linked list, we can only move through the list in one direction.
2. Circular Doubly Linked List:
In circular doubly linked list, each node has two pointers prev and next, similar to doubly
linked list. The prev pointer points to the previous node and the next points to the next
node. Here, in addition to the last node storing the address of the first node, the first node
will also store the address of the last node.

Representation of a Circular Singly Linked List


Let’s take a look on the structure of a circular linked list.

Create/Declare a Node of Circular Linked List


Syntax to Declare a Circular Linked List in Different Languages:
// Node structure
struct Node
{
int data;
struct Node *next;
};

// Function to create a new node


struct Node *createNode(int value){

// Allocate memory
struct Node *newNode =
(struct Node *)malloc(sizeof(struct Node));
// Set the data
newNode->data = value;

// Initialize next to NULL


newNode->next = NULL;

// Return the new node


return newNode;
}
In the code above, each node has data and a pointer to the next node. When we create
multiple nodes for a circular linked list, we only need to connect the last node back to the
first one.
Example of Creating a Circular Linked List
Here’s an example of creating a circular linked list with three nodes (2, 3, 4):

// Allocate memory for nodes


struct Node *first =
(struct Node *)malloc(sizeof(struct Node));
struct Node *second =
(struct Node *)malloc(sizeof(struct Node));
struct Node *last =
(struct Node *)malloc(sizeof(struct Node));

// Initilize nodes
first->data = 2;
second->data = 3;
last->data = 4;

// Connect nodes
first->next = second;
second->next = last;
last->next = first;
In the above code, we have created three nodes first, second, and last having values 2, 3,
and 4 respectively.
 After creating three nodes, we have connected these node in a series.
 Connect the first node “first” to “second” node by storing the address of “second”
node into first’s next
 Connect the second node “second” to “third” node by storing the address of “third”
node into second’s next
 After connecting all the nodes, we reach the key characteristic of a circular linked
list: linking the last node back to the first node. Therefore, we store the address of
the “first” node in the “last” node.
Why have we taken a pointer that points to the last node instead of the first node?
For the insertion of a node at the beginning, we need to traverse the whole list. Also, for
insertion at the end, the whole list has to be traversed. If instead of the start pointer, we
take a pointer to the last node, then in both cases there won’t be any need to traverse the
whole list. So insertion at the beginning or at the end takes constant time, irrespective of
the length of the list.
Operations on the Circular Linked list
We can do some operations on the circular linked list similar to the singly and doubly
linked list which are:
1. Insertion
 Insertion at the empty list
 Insertion at the beginning
 Insertion at the end
 Insertion at the given position
2. Deletion
 Delete the first node
 Delete the last node
 Delete the node from any position
3. Searching
Insertion in the circular linked list
Insertion is a fundamental operation in linked lists that involves adding a new node to the
list. The only extra step is connecting the last node to the first one. In the circular linked
list mentioned below, we can insert nodes in four ways:
1. Insertion in an empty List in the circular linked list
To insert a node in empty circular linked list, creates a new node with the given data, sets
its next pointer to point to itself, and updates the last pointer to reference this new node.

2. Insertion at the beginning in circular linked list


To insert a new node at the beginning of a circular linked list, we create a new node and
check if the list is empty. If empty, the new node points to itself. If not, we make the new
node’s next pointer point to the current head (last->next) and update the last node’s next to
the new node, preserving the circular structure.

3. Insertion at the end in circular linked list


To insert a node at the end of a circular linked list, we create the new node and, if the list is
empty, make it point to itself. Otherwise, we update the tail’s next pointer to the new node
and then set the tail to the new node, preserving the circular linkage.

4. Insertion at specific position in circular linked list


To insert a node at a specific position in a circular linked list, we handle edge cases for an
empty list and invalid positions. For valid positions, we traverse the list and adjust the
pointers to insert the new node, updating the tail if it’s inserted at the end.
Deletion from a Circular Linked List
Deletion involves removing a node from the linked list. The main difference is that we need
to ensure the list remains circular after the deletion. We can delete a node in a circular
linked list in three ways:
1. Delete the first node in circular linked list
To delete the first node of a circular linked list, we check if the list is empty or has only one
node. If so, we handle those cases by deleting the node and updating the last pointer. For
multiple nodes, we update the last node’s next pointer to skip the head and free the head
node, returning the updated last pointer.

2. Delete a specific node in circular linked list


To delete a specific node from a circular linked list, we handle empty list and single node
cases. For other nodes, we use two pointers to find the node, update the previous node’s
next pointer to skip the target, and delete it, updating the last pointer if needed.

3. Deletion at the end of Circular linked list


To delete the last node in a circular linked list, we handle the empty and single node cases.
For multiple nodes, we traverse to find the second last node, update its next pointer to the
head, delete the last node, and return the updated last pointer.
Searching in Circular Linked list
Searching in a circular linked list is similar to searching in a regular linked list. We start at
a given node and traverse the list until you either find the target value or return to the
starting node. Since the list is circular, make sure to keep track of where you started to
avoid an infinite loop.
Advantages of Circular Linked Lists
 In circular linked list, the last node points to the first node. There are no null
references, making traversal easier and reducing the chances of encountering null
pointer exceptions.
 We can traverse the list from any node and return to it without needing to restart
from the head, which is useful in applications requiring a circular iteration.
 Circular linked lists can easily implement circular queues, where the last element
connects back to the first, allowing for efficient resource management.
 In a circular linked list, each node has a reference to the next node in the sequence.
Although it doesn’t have a direct reference to the previous node like a doubly linked
list, we can still find the previous node by traversing the list.
Disadvantages of Circular Linked Lists
 Circular linked lists are more complex to implement than singly linked lists.
 Traversing a circular linked list without a clear stopping condition can lead to infinite
loops if not handled carefully.
 Debugging can be more challenging due to the circular nature, as traditional
methods of traversing linked lists may not apply.
Applications of Circular Linked Lists
 It is used for time-sharing among different users, typically through a Round-Robin
scheduling mechanism.
 In multiplayer games, a circular linked list can be used to switch between players.
After the last player’s turn, the list cycles back to the first player.
 Circular linked lists are often used in buffering applications, such as streaming data,
where data is continuously produced and consumed.
 In media players, circular linked lists can manage playlists, this allowing users to
loop through songs continuously.
 Browsers use circular linked lists to manage the cache. This allows you to navigate
back through your browsing history efficiently by pressing the BACK button.
Applications, Advantages and Disadvantages of Linked List
A Linked List is a linear data structure that is used to store a collection of data with the
help of nodes. Please remember the following points before moving forward.
 The consecutive elements are connected by pointers / references.
 The last node of the linked list points to null.
 The entry point of a linked list is known as the head.
 The common variations of linked lists are Singly, Doubly, Singly Circular and Doubly
Circular.
Advantages of Linked Lists (or Most Common Use Cases):
 Linked Lists are mostly used because of their effective insertion and deletion. We
only need to change few pointers (or references) to insert (or delete) an item in the
middle
 Insertion and deletion at any point in a linked list take O(1) time. Whereas in an
array data structure, insertion / deletion in the middle takes O(n) time.
 This data structure is simple and can be also used to implement a stack, queues,
and other abstract data structures.
 Implementation of Queue and Deque data structures : Simple array implementation
is not efficient at all. We must use circular array to efficiently implement which is
complex. But with linked list, it is easy and straightforward. That is why most of the
language libraries use Linked List internally to implement these data structures..
 Linked List might turn out to be more space efficient compare to arrays in cases
where we cannot guess the number of elements in advance. In case of arrays, the
whole memory for items is allocated together. Even with dynamic sized arrays like
vector in C++ or list in Python or ArrayList in Java. the internal working involves de-
allocation of whole memory and allocation of a bigger chunk when insertions happen
beyond the current capacity.
Applications of Linked Lists:
 Linked Lists can be used to implement stacks, queue, deque, sparse matrices and
adjacency list representation of graphs.
 Dynamic memory allocation in operating systems and compilers (linked list of free
blocks).
 Manipulation of polynomials
 Arithmetic operations on long integers.
 In operating systems, they can be used in Memory management, process scheduling
(for example circular linked list for round robin scheduling) and file system.
 Algorithms that need to frequently insert or delete items from large collections of
data.
 LRU cache, which uses a doubly linked list to keep track of the most recently used
items in a cache.
Applications of Linked Lists in real world:
 The list of songs in the music player are linked to the previous and next songs.
 In a web browser, previous and next web page URLs can be linked through the
previous and next buttons (Doubly Linked List)
 In image viewer, the previous and next images can be linked with the help of the
previous and next buttons (Doubly Linked List)
 Circular Linked Lists can be used to implement things in round manner where we go
to every element one by one.
 Linked List are preferred over arrays for implementations of Queue and Deque data
structures because of fast deletions (or insertions) from the front of the linked lists.
Disadvantages of Linked Lists:
Linked lists are a popular data structure in computer science, but like any other data
structure, they have certain disadvantages as well. Some of the key disadvantages of linked
lists are:
 Slow Access Time: Accessing elements in a linked list can be slow, as you need to
traverse the linked list to find the element you are looking for, which is an O(n)
operation. This makes linked lists a poor choice for situations where you need to
access elements quickly.
 Pointers or References: Linked lists use pointers or references to access the next
node, which can make them more complex to understand and use compared to
arrays. This complexity can make linked lists more difficult to debug and maintain.
 Higher overhead: Linked lists have a higher overhead compared to arrays, as each
node in a linked list requires extra memory to store the reference to the next node.
 Cache Inefficiency: Linked lists are cache-inefficient because the memory is not
contiguous. This means that when you traverse a linked list, you are not likely to get
the data you need in the cache, leading to cache misses and slow performance.
Comparison of Arrays and Linked Lists
Array vs Linked List: Memory Allocation
Array Memory Allocation
In an array, memory is allocated in a contiguous block. This means that all the elements of
the array are stored next to each other in a single, uninterrupted sequence in memory.
When an array is created, a fixed amount of memory is reserved for it, based on the size of
the array and the data type of its elements.
For example, if you create an array of 10 integers, and each integer takes 4 bytes of
memory, then a continuous block of 40 bytes is allocated in memory.
The contiguous memory allocation allows for very fast access to any element in the array.
Since the elements are stored sequentially, the memory address of any element can be
calculated using its index.
Linked List Memory Allocation
In a linked list, memory is allocated in a non-contiguous manner. Each element in a linked
list is stored in a separate node, and these nodes can be located anywhere in memory.
Each node contains two parts: the data and a reference (or pointer) to the next node in the
sequence.
Because the nodes are not required to be stored next to each other, linked lists do not need
a contiguous block of memory.
The non-contiguous allocation gives linked lists a significant advantage in terms of
flexibility. Since nodes are allocated dynamically as needed, the linked list can grow or
shrink in size without the need to allocate or reallocate large blocks of memory.
When to Use Array and Linked List?
Criteria Use Arrays When... Use Linked Lists When...
Memory Memory needs to be allocated in a Memory can be allocated
Allocation contiguous block. dynamically and non-
contiguously.
Access Speed Fast access to elements by index Access speed is not the primary
is required (O(1) access). concern, and traversal is
acceptable.
Size of Data The size of the data structure is The size of the data structure is
Structure fixed or known in advance. dynamic or frequently changing.
Insertion/Deletion Insertions and deletions are Frequent insertions and deletions
infrequent and mostly at the end. are needed, especially at the
beginning or middle.
Memory Efficiency Memory efficiency is important, Slightly higher memory usage is
and overhead from pointers acceptable due to the overhead of
should be minimized. pointers.
Complexity of Simplicity and ease of Flexibility and dynamic memory
Implementation implementation are priorities. management are priorities.
Cache Cache performance is critical, and Cache performance is less critical,
Performance data should be stored and non-contiguous storage is
contiguously for better cache acceptable.
locality.
Sorting and Frequent sorting and searching Sorting and searching are less
Searching operations are required, especially common or handled differently.
binary search (O(log n)).
Fixed Data Types A collection of elements of the A flexible data structure with
same data type is needed. nodes that can store different data
types or complex objects is
needed.
Real-Time The data structure needs to Real-time access is less critical,
Systems support real-time access and and flexibility in structure is more
processing. important.
Array vs Linked List: Time Complexity
Know the difference between array and linked list data structure in terms of time
complexity:
Operation Array Time Linked List Time
Complexity Complexity
Access (by Index) O(1) O(n)
Search (Unsorted) O(n) O(n)
Search (Sorted) O(log n) with Binary O(n)
Search
Insertion (at Beginning) O(n) O(1)
Insertion (at End) O(1) (if space available) O(1) (with tail pointer)
Insertion (at Middle) O(n) O(n)
Deletion (at Beginning) O(n) O(1)
Deletion (at End) O(1) (if space available) O(1) (with tail pointer)
Deletion (at Middle) O(n) O(n)
Array vs Linked List: Space Complexity
Check the difference between array and linked list in terms of space complexity:
Aspect Array Space Complexity Linked List Space Complexity
Storage of O(n) O(n)
Elements
Memory Overhead None O(n) for pointers (additional
overhead)
Dynamic Sizing Fixed size, no dynamic resizing Dynamic size, grows and shrinks as
needed
Efficiency More memory-efficient without Less memory-efficient due to
pointers pointers.
Difference Between Array and Linked List: Comparison
This is a comprehensive comparison showing the difference between array and linked list
with example:
Aspect Array Linked List
Definition A collection of elements stored A collection of elements (nodes)
in a contiguous block of linked by pointers, stored non-
memory. contiguously.
Memory Allocation Contiguous memory block. Non-contiguous memory; each
element is stored in a node with a
pointer to the next.
Types One-dimensional, multi- Singly Linked List, Doubly Linked
dimensional arrays (e.g., 2D, List, Circular Linked List.
3D arrays).
Access Time O(1) – Direct access by index. O(n) – Traversal needed to access
Complexity elements.
Search Time O(n) for unsorted, O(log n) for O(n) – Linear search required.
Complexity sorted (using binary search).
Insertion (at O(n) – All elements must be O(1) – Direct insertion without
Beginning) shifted. shifting.
Insertion (at End) O(1) if space is available; O(n) if O(1) with a tail pointer; O(n) without
array is full (need resizing). a tail pointer.
Deletion (at O(n) – All elements must be O(1) – Direct removal of the first
Beginning) shifted. node.
Deletion (at End) O(1) if space available; O(n) if O(1) with tail pointer; O(n) without a
array is full. tail pointer.
Deletion (at Middle) O(n) – Elements must be O(n) – Traversal required to find and
shifted after deletion. remove the node.
Dynamic Resizing Not flexible – Fixed size; Flexible – Grows and shrinks
resizing requires allocation of dynamically as needed.
new memory block.
Memory Overhead Efficient – Only stores the Higher overhead – Each node
elements. requires additional memory for
pointers.
Cache Performance Better – Contiguous memory Worse – Non-contiguous memory
allocation improves cache reduces cache efficiency.
locality.
Space Complexity O(n) – Space is allocated for n O(n) + O(n) for pointers – Extra
elements. space is needed for pointers in each
node.
Implementation Simple to implement and More complex due to pointer
Complexity manage. management and dynamic memory
allocation.
Best Use Cases - When quick access to - When frequent insertions and
elements is required. deletions are required.
- When the number of elements - When memory usage needs to be
is fixed and known in advance. dynamic and flexible.
Drawbacks - Fixed size, resizing is costly. - Slow access time, extra memory
overhead for pointers.
- Inserting and deleting - Requires traversal for accessing
elements is costly due to elements.
shifting.
Example - Static data storage, tables, - Dynamic memory allocation,
Applications matrices. queues, history management (e.g.,
browser history)

APPLICATIONS OF LINKED LIST


Applications of linked list in computer science:
1. Implementation of stacks and queues
2. Implementation of graphs: Adjacency list representation of graphs is the most
popular which uses a linked list to store adjacent vertices.
3. Dynamic memory allocation: We use a linked list of free blocks.
4. Maintaining a directory of names
5. Performing arithmetic operations on long integers
6. Manipulation of polynomials by storing constants in the node of the linked list
7. Representing sparse matrices
Applications of linked list in the real world:
1. Image viewer – Previous and next images are linked and can be accessed by the next
and previous buttons.
2. Previous and next page in a web browser – We can access the previous and next URL
searched in a web browser by pressing the back and next buttons since they are
linked as a linked list.
3. Music Player – Songs in the music player are linked to the previous and next songs.
So you can play songs either from starting or ending of the list.
4. GPS navigation systems- Linked lists can be used to store and manage a list of
locations and routes, allowing users to easily navigate to their desired destination.
5. Robotics- Linked lists can be used to implement control systems for robots, allowing
them to navigate and interact with their environment.
6. Task Scheduling- Operating systems use linked lists to manage task scheduling,
where each process waiting to be executed is represented as a node in the list.
7. Image Processing- Linked lists can be used to represent images, where each pixel is
represented as a node in the list.
8. File Systems- File systems use linked lists to represent the hierarchical structure of
directories, where each directory or file is represented as a node in the list.
9. Symbol Table- Compilers use linked lists to build a symbol table, which is a data
structure that stores information about identifiers used in a program.
10. Undo/Redo Functionality- Many software applications implement undo/redo
functionality using linked lists, where each action that can be undone is represented
as a node in a doubly linked list.
11. Speech Recognition- Speech recognition software uses linked lists to represent
the possible phonetic pronunciations of a word, where each possible pronunciation is
represented as a node in the list.
12. Polynomial Representation- Polynomials can be represented using linked lists,
where each term in the polynomial is represented as a node in the list.
13. Simulation of Physical Systems- Linked lists can be used to simulate physical
systems, where each element in the list represents a discrete point in time and the
state of the system at that time.
Applications of Circular Linked Lists:
1. Useful for implementation of a queue. Unlike this implementation, we don’t need to
maintain two-pointers for the front and rear if we use a circular linked list. We can
maintain a pointer to the last inserted node and the front can always be obtained as
next of last.
2. Circular lists are useful in applications to go around the list repeatedly. For example,
when multiple applications are running on a PC, it is common for the operating
system to put the running applications on a list and then cycle through them, giving
each of them a slice of time to execute, and then making them wait while the CPU is
given to another application. It is convenient for the operating system to use a
circular list so that when it reaches the end of the list it can cycle around to the front
of the list.
3. Circular Doubly Linked Lists are used for the implementation of advanced data
structures like the Fibonacci Heap.
4. Circular linked lists can be used to implement circular queues, which are often used
in operating systems for scheduling processes and managing memory allocation.
5. Used in database systems to implement linked data structures, such as B+ trees,
which are used to optimize the storage and retrieval of data.
6. Circular linked lists can be used in networking. For instance, to implement circular
buffers for streaming data, such as video and audio, in networking applications.
7. Video games use circular linked lists to manage sprite animations. Each frame of the
animation is represented as a node in the list, and the last frame is connected to the
first frame to create a loop.
8. Circular linked lists can be used to represent a buffer of audio or signal data in
signal processing applications. The last node is connected to the first node to create a
loop, and the processing algorithms can efficiently iterate over the data.
9. Traffic light control systems use circular linked lists to manage the traffic light
cycles. Each phase of the traffic light cycle is represented as a node in the list, and
the last node is connected to the first node to create a loop.
Application of Doubly Linked Lists:
1. Redo and undo functionality.
2. Use of the Back and forward button in a browser.
3. The most recently used section is represented by the Doubly Linked list.
4. Other Data structures like Stack, Hash Table, and Binary Tree can also be applied by
Doubly Linked List.
5. Used to implement game objects and their interactions in a game engine.
6. Used in networking.
7. Used in Graph algorithms.
8. Operating systems use doubly linked lists to manage the process scheduling. Each
process waiting to be executed is represented as a node in the doubly linked list, and
the operating system can easily traverse the list in both directions to manage the
process queue.
Example:
Design a data structure that supports following operations efficiently.
1. getMin : Gets minimum
2. extractMin : Removes minimum
3. getMax : Gets maximum
4. extractMax : Removes maximum
5. insert : Inserts an item. It may be assumed that the inserted item is always greater
than maximum so far. For example, a valid insertion order is 10, 12, 13, 20, 50.
Explanation: Doubly linked list is the best solution here. We maintain head and tail
pointers, since inserted item is always greatest, we insert at tail. Deleting an item from
head or tail can be done in O(1) time. So all operations take O(1) time.
IMPLEMENTATION OF LINKED LISTS
Aim: C program to implement linked lists
Program for Sinlgy Linked List:
#include <stdio.h>
#include <stdlib.h>
// Define the Node structure
struct Node {
int data;
struct Node* next;
};
// Function to create a new node
struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// Function to insert a new element at the beginning of the singly linked list
void insertAtFirst(struct Node** head, int data) {
struct Node* newNode = createNode(data);
newNode->next = *head;
*head = newNode;
}
// Function to insert a new element at the end of the singly linked list
void insertAtEnd(struct Node** head, int data) {
struct Node* newNode = createNode(data);
if (*head == NULL) {
*head = newNode;
return;
}
struct Node* temp = *head;
while (temp->next != NULL) {
temp = temp->next;
}
temp->next = newNode;
}
// Function to insert a new element at a specific position in the singly linked list
void insertAtPosition(struct Node** head, int data, int position) {
struct Node* newNode = createNode(data);
if (position == 0) {
insertAtFirst(head,data);
return;
}
struct Node* temp = *head;
for (int i = 0; temp != NULL && i < position - 1; i++) {
temp = temp->next;
}
if (temp == NULL) {
printf("Position out of range\n");
free(newNode);
return;
}
newNode->next = temp->next;
temp->next = newNode;
}
// Function to delete the first node of the singly linked list
void deleteFromFirst(struct Node** head) {
if (*head == NULL) {
printf("List is empty\n");
return;
}
struct Node* temp = *head;
*head = temp->next;
free(temp);
}
// Function to delete the last node of the singly linked list
void deleteFromEnd(struct Node** head) {
if (*head == NULL) {
printf("List is empty\n");
return;
}
struct Node* temp = *head;
if (temp->next == NULL) {
free(temp);
*head = NULL;
return;
}
while (temp->next->next != NULL) {
temp = temp->next;
}
free(temp->next);
temp->next = NULL;
}
// Function to delete a node at a specific position in the singly linked list
void deleteAtPosition(struct Node** head, int position) {
if (*head == NULL) {
printf("List is empty\n");
return;
}
struct Node* temp = *head;
if (position == 0) {
deleteFromFirst(head);
return;
}
for (int i = 0; temp != NULL && i < position - 1; i++) {
temp = temp->next;
}
if (temp == NULL || temp->next == NULL) {
printf("Position out of range\n");
return;
}
struct Node* next = temp->next->next;
free(temp->next);
temp->next = next;
}
// Function to print the LinkedList
void print(struct Node* head) {
struct Node* temp = head;
while (temp != NULL) {
printf("%d -> ", temp->data);
temp = temp->next;
}
printf("NULL\n");
}
// Driver Code
int main() {
struct Node* head = NULL;
insertAtFirst(&head, 10);
printf("Linked list after inserting the node:10 at the beginning \n");
print(head);
printf("Linked list after inserting the node:20 at the end \n");
insertAtEnd(&head, 20);
print(head);
printf("Linked list after inserting the node:5 at the end \n");
insertAtEnd(&head, 5);
print(head);
printf("Linked list after inserting the node:30 at the end \n");
insertAtEnd(&head, 30);
print(head);
printf("Linked list after inserting the node:15 at position 2 \n");
insertAtPosition(&head, 15, 2);
print(head);
printf("Linked list after deleting the first node: \n");
deleteFromFirst(&head);
print(head);
printf("Linked list after deleting the last node: \n");
deleteFromEnd(&head);
print(head);
printf("Linked list after deleting the node at position 1: \n");
deleteAtPosition(&head, 1);
print(head);
return 0;
}
Output
Linked list after inserting the node:10 at the beginning
10 -> NULL
Linked list after inserting the node:20 at the end
10 -> 20 -> NULL
Linked list after inserting the node:5 at the end
10 -> 20 -> 5 -> NULL
Linked list after inserting the node:30 at the end
10 -> 20 -> 5 -> 30 -> NULL
Linked list after inserting the node:15 at position 2
10 -> 20 -> 15 -> 5 -> 30 -> NULL
Linked list after deleting the first node:
20 -> 15 -> 5 -> 30 -> NULL
Linked list after deleting the last node:
20 -> 15 -> 5 -> NULL
Linked list after deleting the node at position 1:
20 -> 5 -> NULL
Program for Doubly Linked List:
// C Program to Implement Doubly Linked List
#include <stdio.h>
#include <stdlib.h>
// defining a node
typedef struct Node {
int data;
struct Node* next;
struct Node* prev;
} Node;
// Function to create a new node with given value as data
Node* createNode(int data)
{
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = NULL;
newNode->prev = NULL;
return newNode;
}
// Function to insert a node at the beginning
void insertAtBeginning(Node** head, int data)
{
// creating new node
Node* newNode = createNode(data);
// check if DLL is empty
if (*head == NULL) {
*head = newNode;
return;
}
newNode->next = *head;
(*head)->prev = newNode;
*head = newNode;
}
// Function to insert a node at the end
void insertAtEnd(Node** head, int data)
{
// creating new node
Node* newNode = createNode(data);
// check if DLL is empty
if (*head == NULL) {
*head = newNode;
return;
}
Node* temp = *head;
while (temp->next != NULL) {
temp = temp->next;
}
temp->next = newNode;
newNode->prev = temp;
}
// Function to insert a node at a specified position
void insertAtPosition(Node** head, int data, int position)
{
if (position < 1) {
printf("Position should be >= 1.\n");
return;
}
// if we are inserting at head
if (position == 1) {
insertAtBeginning(head, data);
return;
}
Node* newNode = createNode(data);
Node* temp = *head;
for (int i = 1; temp != NULL && i < position - 1; i++) {
temp = temp->next;
}
if (temp == NULL) {
printf(
"Position greater than the number of nodes.\n");
return;
}
newNode->next = temp->next;
newNode->prev = temp;
if (temp->next != NULL) {
temp->next->prev = newNode;
}
temp->next = newNode;
}
// Function to delete a node from the beginning
void deleteAtBeginning(Node** head)
{
// checking if the DLL is empty
if (*head == NULL) {
printf("The list is already empty.\n");
return;
}
Node* temp = *head;
*head = (*head)->next;
if (*head != NULL) {
(*head)->prev = NULL;
}
free(temp);
}
// Function to delete a node from the end
void deleteAtEnd(Node** head)
{
// checking if DLL is empty
if (*head == NULL) {
printf("The list is already empty.\n");
return;
}
Node* temp = *head;
if (temp->next == NULL) {
*head = NULL;
free(temp);
return;
}
while (temp->next != NULL) {
temp = temp->next;
}
temp->prev->next = NULL;
free(temp);
}
// Function to delete a node from a specified position
void deleteAtPosition(Node** head, int position)
{
if (*head == NULL) {
printf("The list is already empty.\n");
return;
}
Node* temp = *head;
if (position == 1) {
deleteAtBeginning(head);
return;
}
for (int i = 1; temp != NULL && i < position; i++) {
temp = temp->next;
}
if (temp == NULL) {
printf("Position is greater than the number of "
"nodes.\n");
return;
}
if (temp->next != NULL) {
temp->next->prev = temp->prev;
}
if (temp->prev != NULL) {
temp->prev->next = temp->next;
}
free(temp);
}
// Function to print the list in forward direction
void printListForward(Node* head)
{
Node* temp = head;
printf("Forward List: ");
while (temp != NULL) {
printf("%d ", temp->data);
temp = temp->next;
}
printf("\n");
}
// Function to print the list in reverse direction
void printListReverse(Node* head)
{
Node* temp = head;
if (temp == NULL) {
printf("The list is empty.\n");
return;
}
// Move to the end of the list
while (temp->next != NULL) {
temp = temp->next;
}
// Traverse backwards
printf("Reverse List: ");
while (temp != NULL) {
printf("%d ", temp->data);
temp = temp->prev;
}
printf("\n");
}
int main()
{
Node* head = NULL;

// Demonstrating various operations


insertAtEnd(&head, 10);
insertAtEnd(&head, 20);
insertAtBeginning(&head, 5);
insertAtPosition(&head, 15, 2); // List: 5 15 10 20
printf("After Insertions:\n");
printListForward(head);
printListReverse(head);
deleteAtBeginning(&head); // List: 15 10 20
deleteAtEnd(&head); // List: 15 10
deleteAtPosition(&head, 2); // List: 15
printf("After Deletions:\n");
printListForward(head);
return 0;
}
Output
After Insertions:
Forward List: 5 15 10 20
Reverse List: 20 10 15 5
After Deletions:
Forward List: 15
Program for Circular Linked List:
#include <stdio.h>
#include <stdlib.h>
// Node structure
struct Node {
int data;
struct Node* next;
};
// Function to create a new node
struct Node* createNode(int data)
{
struct Node* newNode
= (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// Function to insert a node at the beginning
void insertAtBeginning(struct Node** head, int data)
{
struct Node* newNode = createNode(data);
if (*head == NULL) {
*head = newNode;
newNode->next = *head;
}
else {
struct Node* temp = *head;
while (temp->next != *head) {
temp = temp->next;
}
temp->next = newNode;
newNode->next = *head;
*head = newNode;
}
}
// Function to insert a node at the end
void insertAtEnd(struct Node** head, int data)
{
struct Node* newNode = createNode(data);
if (*head == NULL) {
*head = newNode;
newNode->next = *head;
}
else {
struct Node* temp = *head;
while (temp->next != *head) {
temp = temp->next;
}
temp->next = newNode;
newNode->next = *head;
}
}
// Function to insert a node at a specific position
void insertAtPosition(struct Node** head, int data,
int position)
{
struct Node* newNode = createNode(data);
if (*head == NULL && position == 0) {
*head = newNode;
newNode->next = *head;
}
else if (position == 0) {
insertAtBeginning(head, data);
}
else {
struct Node* temp = *head;
int i = 0;
while (i < position - 1) {
temp = temp->next;
i++;
}
newNode->next = temp->next;
temp->next = newNode;
}
}
// Function to delete a node from the beginning
void deleteFromBeginning(struct Node** head)
{
if (*head == NULL) {
return;
}
else if ((*head)->next == *head) {
free(*head);
*head = NULL;
}
else {
struct Node* temp = *head;
while (temp->next != *head) {
temp = temp->next;
}
temp->next = (*head)->next;
struct Node* toDelete = *head;
*head = (*head)->next;
free(toDelete);
}
}
// Function to delete a node from the end
void deleteFromEnd(struct Node** head)
{
if (*head == NULL) {
return;
}
else if ((*head)->next == *head) {
free(*head);
*head = NULL;
}
else {
struct Node* secondLast = *head;
while (secondLast->next->next != *head) {
secondLast = secondLast->next;
}
struct Node* last = secondLast->next;
secondLast->next = *head;
free(last);
}
}
// Function to delete a node from a specific position
void deleteAtPosition(struct Node** head, int position)
{
if (*head == NULL) {
return;
}
else if (position == 0) {
deleteFromBeginning(head);
}
else {
struct Node* temp = *head;
int i = 0;
while (i < position - 1) {
temp = temp->next;
i++;
}
struct Node* toDelete = temp->next;
temp->next = temp->next->next;
free(toDelete);
}
}
// Function to traverse and print the circular linked list
void traverse(struct Node* head)
{
if (head == NULL) {
return;
}
struct Node* temp = head;
do {
printf("%d -> ", temp->data);
temp = temp->next;
} while (temp != head);
printf("HEAD\n");
}
// Function to search for a node with a given key
int search(struct Node* head, int key)
{
if (head == NULL) {
return 0;
}
struct Node* temp = head;
do {
if (temp->data == key) {
return 1; // Key found
}
temp = temp->next;
} while (temp != head);
return 0; // Key not found
}
// Driver program
int main()
{
struct Node* head = NULL;
// Insertion
insertAtEnd(&head, 10);
insertAtEnd(&head, 20);
insertAtBeginning(&head, 5);
insertAtPosition(&head, 15, 2);
// Traversal
printf("Circular Linked List: ");
traverse(head);
// Deletion
deleteFromEnd(&head);
deleteAtPosition(&head, 1);
// Traversal after deletion
printf("Circular Linked List after deletion: ");
traverse(head);
// Searching
int key = 10;
if (search(head, key)) {
printf("Element %d is found in the linked list.\n",
key);
}
else {
printf(
"Element %d is not found in the linked list.\n",
key);
}
return 0;
}
Output
Circular Linked List: 5 -> 10 -> 15 -> 20 -> HEAD
Circular Linked List after deletion: 5 -> 15 -> HEAD
Element 10 is not found in the linked list.
INTRODUCTION TO STACKS: PROPERTIES AND OPERATIONS
What is Stack Data Structure?
Stack is a linear data structure that follows LIFO (Last In First Out) Principle, the last
element inserted is the first to be popped out. It means both insertion and deletion
operations happen at one end only.

LIFO(Last In First Out) Principle


Here are some real world examples of LIFO
 Consider a stack of plates. When we add a plate, we add at the top. When we remove,
we remove from the top.
 A shuttlecock box (or any other box that is closed from one end) is another great
real-world example of the LIFO (Last In, First Out) principle where do insertions
and removals from the same end.

Representation of Stack Data Structure:

Stack follows LIFO (Last In First Out) Principle so the element which is pushed last is
popped first.

Types of Stack:

 Fixed Size Stack : As the name suggests, a fixed size stack has a fixed size and
cannot grow or shrink dynamically. If the stack is full and an attempt is made to add
an element to it, an overflow error occurs. If the stack is empty and an attempt is
made to remove an element from it, an underflow error occurs.
 Dynamic Size Stack : A dynamic size stack can grow or shrink dynamically. When
the stack is full, it automatically increases its size to accommodate the new element,
and when the stack is empty, it decreases its size. This type of stack is implemented
using a linked list, as it allows for easy resizing of the stack.

Basic Operations on Stack:

In order to make manipulations in a stack, there are certain operations provided to us.
 push() to insert an element into the stack
 pop() to remove an element from the stack
 top() Returns the top element of the stack.
 isEmpty() returns true if stack is empty else false.
 isFull() returns true if the stack is full else false.
To implement stack, we need to maintain reference to the top item.
Push Operation on Stack
Adds an item to the stack. If the stack is full, then it is said to be an Overflow condition.
Algorithm for Push Operation:
 Before pushing the element to the stack, we check if the stack is full .
 If the stack is full (top == capacity-1) , then Stack Overflows and we cannot insert
the element to the stack.
 Otherwise, we increment the value of top by 1 (top = top + 1) and the new value is
inserted at top position.
 The elements can be pushed into the stack till we reach the capacity of the stack.

Pop Operation in Stack


Removes an item from the stack. The items are popped in the reversed order in which they
are pushed. If the stack is empty, then it is said to be an Underflow condition.
Algorithm for Pop Operation:
 Before popping the element from the stack, we check if the stack is empty.
 If the stack is empty (top == -1), then Stack Underflows and we cannot remove any
element from the stack.
 Otherwise, we store the value at top, decrement the value of top by 1 (top = top – 1)
and return the stored top value.

Top or Peek Operation on Stack


Returns the top element of the stack.
Algorithm for Top Operation:
 Before returning the top element from the stack, we check if the stack is empty.
 If the stack is empty (top == -1), we simply print “Stack is empty”.
 Otherwise, we return the element stored at index = top.

isEmpty Operation in Stack Data Structure:


Returns true if the stack is empty, else false.
Algorithm for isEmpty Operation:
 Check for the value of top in stack.
 If (top == -1), then the stack is empty so return true.
 Otherwise, the stack is not empty so return false.
isFull Operation in Stack Data Structure:
Returns true if the stack is full, else false.
Algorithm for isFull Operation:
 Check for the value of top in stack.
 If (top == capacity-1), then the stack is full so return true.
 Otherwise, the stack is not full so return false.

IMPLEMENT STACK USING ARRAY


Stack is a linear data structure which follows LIFO principle. In this article, we will learn
how to implement Stack using Arrays. In Array-based approach, all stack-related
operations are executed using arrays. Let’s see how we can implement each operation on
the stack utilizing the Array Data Structure.

Implement Stack using Array:

To implement a stack using an array, initialize an array and treat its end as the stack’s top.
Implement push (add to end), pop (remove from end), and peek (check end) operations,
handling cases for an empty or full stack.
Step-by-step approach:
1. Initialize an array to represent the stack.
2. Use the end of the array to represent the top of the stack.
3. Implement push (add to end), pop (remove from the end), and peek (check end)
operations, ensuring to handle empty and full stack conditions.
IMPLEMENTATION OF STACK USING ARRAYS
Here are the following operations of implement stack using array:
Push Operation in Stack:
Adds an item to the stack. If the stack is full, then it is said to be an Overflow condition.
Algorithm for Push Operation:
 Before pushing the element to the stack, we check if the stack is full .
 If the stack is full (top == capacity-1) , then Stack Overflows and we cannot insert
the element to the stack.
 Otherwise, we increment the value of top by 1 (top = top + 1) and the new value is
inserted at top position .
 The elements can be pushed into the stack till we reach the capacity of the stack.

Pop Operation in Stack:


Removes an item from the stack. The items are popped in the reversed order in which they
are pushed. If the stack is empty, then it is said to be an Underflow condition.
Algorithm for Pop Operation:
 Before popping the element from the stack, we check if the stack is empty .
 If the stack is empty (top == -1), then Stack Underflows and we cannot remove any
element from the stack.
 Otherwise, we store the value at top, decrement the value of top by 1 (top = top –
1) and return the stored top value.

Top or Peek Operation in Stack:


Returns the top element of the stack.
Algorithm for Top Operation:
 Before returning the top element from the stack, we check if the stack is empty.
 If the stack is empty (top == -1), we simply print “Stack is empty”.
 Otherwise, we return the element stored at index = top .
isEmpty Operation in Stack:
Returns true if the stack is empty, else false.
Algorithm for isEmpty Operation :
 Check for the value of top in stack.
 If (top == -1) , then the stack is empty so return true .
 Otherwise, the stack is not empty so return false .
isFull Operation in Stack :
Returns true if the stack is full, else false.
Algorithm for isFull Operation:
 Check for the value of top in stack.
 If (top == capacity-1), then the stack is full so return true .
 Otherwise, the stack is not full so return false.
Below is the implementation of the above approach:
PROGRAM:
// C program for array implementation of stack
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
// A structure to represent a stack
struct Stack {
int top;
unsigned capacity;
int* array;
};
// function to create a stack of given capacity. It initializes size of
// stack as 0
struct Stack* createStack(unsigned capacity)
{
struct Stack* stack = (struct Stack*)malloc(sizeof(struct Stack));
stack->capacity = capacity;
stack->top = -1;
stack->array = (int*)malloc(stack->capacity * sizeof(int));
return stack;
}
// Stack is full when top is equal to the last index
int isFull(struct Stack* stack)
{
return stack->top == stack->capacity - 1;
}
// Stack is empty when top is equal to -1
int isEmpty(struct Stack* stack)
{
return stack->top == -1;
}
// Function to add an item to stack. It increases top by 1
void push(struct Stack* stack, int item)
{
if (isFull(stack))
return;
stack->array[++stack->top] = item;
printf("%d pushed to stack\n", item);
}
// Function to remove an item from stack. It decreases top by 1
int pop(struct Stack* stack)
{
if (isEmpty(stack))
return INT_MIN;
return stack->array[stack->top--];
}
// Function to return the top from stack without removing it
int peek(struct Stack* stack)
{
if (isEmpty(stack))
return INT_MIN;
return stack->array[stack->top];
}
// Driver program to test above functions
int main()
{
struct Stack* stack = createStack(100);
push(stack, 10);
push(stack, 20);
push(stack, 30);
printf("%d popped from stack\n", pop(stack));
return 0;
}
Output
10 pushed into stack
20 pushed into stack
30 pushed into stack
30 Popped from stack
Top element is : 20
Elements present in stack : 20 10
Complexity Analysis:
 Time Complexity:
o push: O(1)
o pop: O(1)
o peek: O(1)
o is_empty: O(1)
o is_full: O(1)
 Auxiliary Space: O(n), where n is the number of items in the stack.

Advantages of Array Implementation:

 Easy to implement.
 Memory is saved as pointers are not involved.

Disadvantages of Array Implementation:

 It is not dynamic i.e., it doesn’t grow and shrink depending on needs at runtime. [But
in case of dynamic sized arrays like vector in C++, list in Python, ArrayList in Java,
stacks can grow and shrink with array implementation as well].
 The total size of the stack must be defined beforehand.
IMPLEMENTATION OF STACK USING LINKED LIST
Stack is a linear data structure that follows the Last-In-First-Out (LIFO) order of
operations. This means the last element added to the stack will be the first one to be
removed. There are different ways using which we can implement stack data structure in
C.
In this article, we will learn how to implement a stack using a linked list in C, its basic
operation along with their time and space complexity analysis.
Implementation of Stack using Linked List in C
Stack is generally implemented using an array but the limitation of this kind of stack is
that the memory occupied by the array is fixed no matter what are the number of elements
in the stack. In the stack implemented using linked list implementation, the size occupied
by the linked list will be equal to the number of elements in the stack. Moreover, its size is
dynamic. It means that the size is gonna change automatically according to the elements
present.

Representation of Linked Stack in C


In C, the stack that is implemented using a linked list can be represented by the pointer to
the head node of the linked list. Each node in that linked list represents the element of the
stack. The type of linked list here is a singly linked list in which each node consists of a
data field and the next pointer.
struct Node {
type data;
Node* next;
}
The type of data can be defined according to the requirement.

Basic Operations of Linked List Stack in C


Following are the basic operation of the stack data structure that helps us to manipulate
the data structure as needed:
Push Function
The push function will add a new element to the stack. As the pop function require the
time and space complexity to be O(1), we will insert the new element in the beginning of the
linked list. In multiple push operations, the element at the head will be the element that is
most recently inserted.
We need to check for stack overflow (when we try to push into stack when it is already full).
Algorithm for Push Function
Following is the algorithm for the push function:
 Create a new node with the given data.
 Insert the node before the head of the linked list.
 Update the head of the linked list to the new node.
Here, the stack overflow will only occur if there is some error allocating the memory in the
heap so the check will be done in the creation of the new node.
Pop Function
The pop function will remove the topmost element from the stack. As we know that for
stack, we need to first remove the most recently inserted element. In this implementation,
the most recently inserted element will be present at the head of the linked list.
We first need to check for the stack underflow (when we try to pop stack when it is already
empty).
Algorithm for Pop Function
Following is the algorithm for the pop function:
 Check if the stack is empty.
 If not empty, store the top node in a temporary variable.
 Update the head pointer to the next node.
 Free the temporary node.
Peek Function
The peek function will return the topmost element of the stack if the stack is not empty.
The topmost element means the element at the head.
Algorithm for Peek Function
The following is the algorithm for peek function:
 Check if the stack is empty.
 If its empty, return -1.
 Else return the head->data.
IsEmpty Function
The isEmpty function will check if the stack is empty or not. This function returns true if
the stack is empty otherwise, it returns false.
Algorithm of isEmpty Function
The following is the algorithm for isEmpty function:
 Check if the top pointer of the stack is NULL.
 If NULL, return true, indicating the stack is empty.
 Otherwise return false indicating the stack is not empty.
PROGRAM:
// C program to implement a stack using linked list
#include <stdio.h>
#include <stdlib.h>
// ________LINKED LIST UTILITY FUNCITON____________
// Define the structure for a node of the linked list
typedef struct Node {
int data;
struct Node* next;
} node;
// linked list utility function
node* createNode(int data)
{
// allocating memory
node* newNode = (node*)malloc(sizeof(node));
// if memory allocation is failed
if (newNode == NULL)
return NULL;
// putting data in the node
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// fuction to insert data before the head node
int insertBeforeHead(node** head, int data)
{
// creating new node
node* newNode = createNode(data);
// if malloc fail, return error code
if (!newNode)
return -1;
// if the linked list is empty
if (*head == NULL) {
*head = newNode;
return 0;
}
newNode->next = *head;
*head = newNode;
return 0;
}
// deleting head node
int deleteHead(node** head)
{
// no need to check for empty stack as it is already
// being checked in the caller function
node* temp = *head;
*head = (*head)->next;
free(temp);
return 0;
}
// _________STACK IMPLEMENTATION STARTS HERE_________
// Function to check if the stack is empty or not
int isEmpty(node** stack) { return *stack == NULL; }
// Function to push elements to the stack
void push(node** stack, int data)
{
// inserting the data at the beginning of the linked
// list stack
// if the insertion function returns the non - zero
// value, it is the case of stack overflow
if (insertBeforeHead(stack, data)) {
printf("Stack Overflow!\n");
}
}
// Function to pop an element from the stack
int pop(node** stack)
{
// checking underflow condition
if (isEmpty(stack)) {
printf("Stack Underflow\n");
return -1;
}
// deleting the head.
deleteHead(stack);
}
// Function to return the topmost element of the stack
int peek(node** stack)
{
// check for empty stack
if (!isEmpty(stack))
return (*stack)->data;
else
return -1;
}
// Function to print the Stack
void printStack(node** stack)
{
node* temp = *stack;
while (temp != NULL) {
printf("%d-> ", temp->data);
temp = temp->next;
}
printf("\n");
}
// driver code
int main()
{
// Initialize a new stack top pointer
node* stack = NULL;
// Push elements into the stack
push(&stack, 10);
push(&stack, 20);
push(&stack, 30);
push(&stack, 40);
push(&stack, 50);
// Print the stack
printf("Stack: ");
printStack(&stack);
// Pop elements from the stack
pop(&stack);
pop(&stack);
// Print the stack after deletion of elements
printf("\nStack: ");
printStack(&stack);
return 0;
}
Output
Stack: 50-> 40-> 30-> 20-> 10->

Stack: 30-> 20-> 10->


Benefits of Linked List Stack in C
The following are the major benefits of the linked list implementation over the array
implementation:
1. The dynamic memory management of linked list provide dynamic size to the stack
that changes with the change in the number of elements.
2. Rarely reaches the condition of the stack overflow.

You might also like