Application of Circular Linked Lists:
Implementation of a Queue: A circular linked list can be used to
implement a queue, where the front and rear of the queue are the first
and last nodes of the list, respectively. In this implementation, when an
element is enqueued, it is added to the rear of the list and when an
element is dequeued, it is removed from the front of the list.
Music or Media Player: Circular linked lists can be used to create a
playlist for a music or media player. The playlist can be represented as a
circular linked list, where each node contains information about a song or
media item, and the next node points to the next song in the playlist. This
allows for continuous playback of the playlist.
Hash table implementation: Circular linked lists can be used to
implement a hash table, where each index in the table is a circular linked
list. This is a form of collision resolution, where when two keys hash to the
same index, the nodes are added to the circular linked list at that index.
Memory allocation: In computer memory management, circular linked
lists can be used to keep track of allocated and free blocks of memory.
Each node in the list represents a block of memory and its status, with the
next node pointing to the next block of memory. When a block of memory
is freed, it can be added back to the circular linked list.
Navigation systems: Circular linked lists can be used to model the
movements of vehicles on a circular route, such as buses, trains or
planes that travel in a loop or circular route. Each node in the list
represents the location of the vehicle on the route, with the next node
pointing to the next location on the route.
Real-Life Application of Circular Linked Lists:
Music and Media Players: Circular linked lists can be used to implement
playlists in music and media players. Each node in the list can represent a
song or media item, and the “next” pointer can point to the next song in
the playlist. When the end of the playlist is reached, the pointer can be set
to the beginning of the list, creating a circular structure that allows for
continuous playback.
Task Scheduling: Circular linked lists can be used to implement task
scheduling algorithms, where each node in the list represents a task and
its priority. The “next” pointer can point to the next task in the queue, with
the end of the queue pointing back to the beginning to create a circular
structure. This allows for a continuous loop of task scheduling, where
tasks are added and removed from the queue based on their priority.
Cache Management: Circular linked lists can be used in cache
management algorithms to manage the replacement of cache entries.
Each node in the list can represent a cache entry, with the “next” pointer
pointing to the next entry in the list. When the end of the list is reached,
the pointer can be set to the beginning of the list, creating a circular
structure that allows for the replacement of older entries with newer ones.
File System Management: Circular linked lists can be used in file system
management to track the allocation of disk space. Each node in the list
can represent a block of disk space, with the “next” pointer pointing to the
next available block. When the end of the list is reached, the pointer can
be set to the beginning of the list, creating a circular structure that allows
for the allocation and deallocation of disk space.
Advantages of Circular Linked Lists:
Efficient operations: Since the last node of the list points back to the first
node, circular linked lists can be traversed quickly and efficiently. This
makes them useful for applications that require frequent traversal, such
as queue and hash table implementations.
Space efficiency: Circular linked lists can be more space-efficient than
other types of linked lists because they do not require a separate pointer
to keep track of the end of the list. This means that circular linked lists can
be more compact and take up less memory than other types of linked
lists.
Flexibility: The circular structure of the list allows for greater flexibility in
certain applications. For example, a circular linked list can be used to
represent a ring or circular buffer, where new elements can be added and
old elements can be removed without having to shift the entire list.
Dynamic size: Circular linked lists can be dynamically sized, which
means that nodes can be added or removed from the list as needed. This
makes them useful for applications where the size of the list may change
frequently, such as memory allocation and music or media players.
Ease of implementation: Implementing circular linked lists is often
simpler than implementing other types of linked lists. This is because
circular linked lists have a simple, circular structure that is easy to
understand and implement.
Disadvantages of Circular Linked Lists:
Complexity: Circular linked lists can be more complex than other types of
linked lists, especially when it comes to algorithms for insertion and
deletion operations. For example, determining the correct position for a
new node can be more difficult in a circular linked list than in a linear
linked list.
Memory leaks: If the pointers in a circular linked list are not managed
properly, memory leaks can occur. This happens when a node is removed
from the list but its memory is not freed, leading to a buildup of unused
memory over time.
Traversal can be more difficult: While traversal of a circular linked list
can be efficient, it can also be more difficult than linear traversal,
especially if the circular list has a complex structure. Traversing a circular
linked list requires careful management of the pointers to ensure that
each node is visited exactly once.
Lack of a natural end: The circular structure of the list can make it
difficult to determine when the end of the list has been reached. This can
be a problem in certain applications, such as when processing a list of
data in a linear fashion.
Convert singly linked list into circular
linked list
Given a singly linked list, we have to convert it into circular linked
list. For example, we have been given a singly linked list with four
nodes and we want to convert this singly linked list into circular
linked list.
The above singly linked list is converted into circular linked list.
Approach: The idea is to traverse the singly linked list and check if
the node is the last node or not. If the node is the last node i.e
pointing to NULL then make it point to the starting node i.e head
node. Below is the implementation of this approach.
Algorithm:
Here are the algorithmic steps to convert a singly linked list
into a circular linked list:
Step 1 : Initialize a pointer current to the head node of the
singly linked list.
Step 2 : Traverse the linked list by moving the current
pointer to the next node until it
reaches the last node (i.e., the node whose next
pointer is NULL).
Step 3 : Set the next pointer of the last node to point back
to the head node
of the linked list.
The singly linked list is now a circular linked list.
Note that step 3 is what actually converts the singly linked
list into a circular linked list.
What is Stack?
A stack is a linear data structure in which the insertion of a new element and removal
of an existing element takes place at the same end represented as the top of the stack.
To implement the stack, it is required to maintain the pointer to the top of the stack,
which is the last element to be inserted because we can access the elements only on
the top of the stack.
LIFO( Last In First Out ):
This strategy states that the element that is inserted last will come out first.
You can take a pile of plates kept on top of each other as a real-life example.
The plate which we put last is on the top and since we remove the plate that
is at the top, we can say that the plate that was put last comes out first.
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.
size() returns the size of stack.
Stack
Push:
Adds an item to the stack. If the stack is full, then it is said to be an Overflow
condition.
Algorithm for push:
begin
if stack is full
return
endif
else
increment top
stack[top] assign value
end else
end procedure
Pop:
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:
begin
if stack is empty
return
endif
else
store value of stack[top]
decrement top
return value
end else
end procedure
Top:
Returns the top element of the stack.
Algorithm for Top:
begin
return stack[top]
end procedure
isEmpty:
Returns true if the stack is empty, else false.
Algorithm for isEmpty:
begin
if top < 1
return true
else
return false
end procedure
Understanding stack practically:
There are many real-life examples of a stack. Consider the simple example
of plates stacked over one another in a canteen. The plate which is at the top
is the first one to be removed, i.e. the plate which has been placed at the
bottommost position remains in the stack for the longest period of time. So, it
can be simply seen to follow the LIFO/FILO order.
Complexity Analysis:
Time Complexity
Operations Complexity
push() O(1)
pop() O(1)
isEmpty() O(1)
size() O(1)
Types of Stacks:
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.
In addition to these two main types, there are several other
variations of Stacks, including:
1. Infix to Postfix Stack: This type of stack is used to convert infix
expressions to postfix expressions.
2. Expression Evaluation Stack: This type of stack is used to
evaluate postfix expressions.
3. Recursion Stack: This type of stack is used to keep track of
function calls in a computer program and to return control to the
correct function when a function returns.
4. Memory Management Stack: This type of stack is used to store
the values of the program counter and the values of the registers
in a computer program, allowing the program to return to the
previous state when a function returns.
5. Balanced Parenthesis Stack: This type of stack is used to
check the balance of parentheses in an expression.
6. Undo-Redo Stack: This type of stack is used in computer
programs to allow users to undo and redo actions.
Applications of the stack:
Infix to Postfix /Prefix conversion
Redo-undo features at many places like editors, photoshop.
Forward and backward features in web browsers
Used in many algorithms like Tower of Hanoi, tree
traversals, stock span problems, and histogram problems.
Backtracking is one of the algorithm designing techniques. Some
examples of backtracking are the Knight-Tour problem, N-Queen
problem, find your way through a maze, and game-like chess or
checkers in all these problems we dive into someway if that way
is not efficient we come back to the previous state and go into
some another path. To get back from a current state we need to
store the previous state for that purpose we need a stack.
In Graph Algorithms like Topological Sorting and Strongly
Connected Components
In Memory management, any modern computer uses a stack as
the primary management for a running purpose. Each program
that is running in a computer system has its own memory
allocations
String reversal is also another application of stack. Here one by
one each character gets inserted into the stack. So the first
character of the string is on the bottom of the stack and the last
element of a string is on the top of the stack. After Performing the
pop operations on the stack we get a string in reverse order.
Stack also helps in implementing function call in computers. The
last called function is always completed first.
Stacks are also used to implement the undo/redo operation in
text editor.
Implementation of Stack:
A stack can be implemented using an array or a linked list.
In an array-based implementation, the push operation is
implemented by incrementing the index of the top element and
storing the new element at that index.
The pop operation is implemented by decrementing the index of the
top element and returning the value stored at that index.
In a linked list-based implementation, the push operation is
implemented by creating a new node with the new element and
setting the next pointer of the current top node to the new node.
The pop operation is implemented by setting the next pointer of the
current top node to the next node and returning the value of the
current top node.
Stacks are commonly used in computer science for a variety of
applications, including The evaluation of expressions,
function calls,
memory management.
In the evaluation of expressions, a stack can be used to store
operands and operators as they are processed.
In function calls, a stack can be used to keep track of the order in
which functions are called and to return control to the correct
function when a function returns
. In memory management, a stack can be used to store the values of
the program counter and the values of the registers in a computer
program, allowing the program to return to the previous state when
a function returns.
Using array
Using linked list
// 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;
}
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.
Advantages of Linked List implementation:
The linked list implementation of a stack can grow and shrink
according to the needs at runtime.
It is used in many virtual machines like JVM.
Disadvantages of Linked List implementation:
Requires extra memory due to the involvement of pointers.
Random accessing is not possible in stack.