DS Unit 2
DS Unit 2
Linked Lists: Singly linked lists: representation and operations, doubly linked lists and circular
linked lists, Comparing arrays and linked lists, Applications of linked lists.
Singly linked lists
A singly linked list is a fundamental data structure that consists of a sequence of elements, where
each element points to the next one in the sequence in which the elements are not stored in
contiguous memory locations. Each element in the list is called a "node," and it contains two
components: the data (or payload) and a reference (or link) to the next node in the sequence. The
last node typically points to null, indicating the end of the list.
b. Code Representation:
In programming languages, we use code structures to represent linked lists. Here's an example in
struct Node
{
int data; // Data field
struct Node* next; // Next pointer
};
This code defines a structure called Node that represents a single node in the linked list.
The data field stores the information, and the next pointer is a variable of type struct Node*,
meaning it can hold the address of another Node structure.
The head node is the first node in the list and has a next pointer that typically points to the
second node, or NULL if the list is empty. The end of the list is marked by a node with a next
pointer set to NULL. Traversing the list involves starting from the head and following the next
pointers until you reach a node with NULL in its next pointer.
Understanding both the in-memory representation and the code representation is essential
for effectively working with single linked lists.
List operations on linked list
In a linked list, each element (node) contains two parts: data and a reference (or pointer) to
the next node in the sequence. Here are the fundamental list operations commonly performed on a
linked list:
Insertion:
Insert at the Beginning: Create a new node with the given data, set its next pointer to the
current head, and update the head to point to the new node.
Insert at the End: Traverse the list until reaching the last node, then create a new node with
the given data and set the next pointer of the last node to point to the new node.
Insert at a Specific Position: Traverse the list until reaching the desired position, then
perform the insertion similar to inserting at the beginning or end.
Deletion:
Delete from the Beginning: Update the head to point to the next node and free the memory
allocated for the old head.
Delete from the End: Traverse the list until reaching the second-to-last node, update its next
pointer to NULL, and free the memory allocated for the last node.
Delete from a Specific Position: Traverse the list until reaching the node before the desired
position, update its next pointer to skip the desired node, and free the memory allocated for
the deleted node.
Traversal: Traverse the list from the head node to the end while printing or processing each node's
data.
Searching: Traverse the list from the head node to the end while comparing each node's data with
the target value until a match is found or the end of the list is reached.
Size/Length: Traverse the list while counting the number of nodes until reaching the end.
Insert at the Beginning linked list
Inserting a node at the beginning of a linked list involves creating a new node, setting its next
pointer to point to the current head of the list, and updating the head to point to the new node.
Here's how you can implement this operation in C:
void insertAtFront(int x)
{
struct Node* t;
t = (struct Node*)malloc(sizeof(struct Node));
t->data = x;
t->next = head;
head = t;
}
In this code:
We define a structure Node to represent each node of the linked list. It contains an integer data
field and a pointer next to the next node.
The function insertAtFront takes the data for the new node (x). It allocates memory for the new
node ‘t’, assigns the data, updates the next pointer to point to the current head, and updates the
head to point to the new node ‘t’.
Insert at the End of the linked list
To insert a node at the end of a linked list, you need to traverse the list until you reach the last
node, create a new node with the given data, and set the next pointer of the last node to point to
the new node. Here's how you can implement this operation in C:
void insertAtRear(int x)
{
struct Node* t, *temp;
t = (struct Node*)malloc(sizeof(struct Node));
temp=head;
t->data=x;
t->next=NULL;
if(head==NULL)
head=t;
else
{
while(temp->next!=NULL)
temp=temp->next;
temp->next=t;
}
}
In this code:
We define a structure Node to represent each node of the linked list. It contains an integer data
field and a pointer next to the next node.
The function insertAtRear takes the data for the new node (x). It allocates memory for the new
node, assigns the data, and sets its next pointer to NULL since it will be the last node. If the list is
empty, the new node becomes the head. Otherwise, it traverses the list until reaching the last node
and sets the next pointer of the last node to point to the new node.
The function insertAtPosition takes the data for the new node (x), and the position where the new
node should be inserted (pos). If the position is 1 or the list is empty, the new node is inserted at
the beginning. Otherwise, it traverses the list until reaching the node before the desired position,
inserts the new node after that node, and adjusts the pointers accordingly. If the position is out of
range, it just inserts the element at the end of the linked list.
Delete the first element (head) of a linked list
To delete the first element (head) of a linked list, you need to update the head pointer to point to
the second node (if it exists) and free the memory allocated for the first node. Here's how you can
implement this operation in C:
void deleteAtFront()
{
struct Node *temp;
temp=head;
if(head==NULL)
printf("List is empty\n");
else
{
head=head->next;
printf("Deleted Element is %d\n",temp->data);
free(temp);
}
}
In this code:
The function deleteAtFront, checks if the list is empty. If the list is empty, the function just outputs
“List is empty”. If not, it stores a reference to the current head node (temp), updates the head to
point to the second node (if it exists), and frees the memory allocated for the old head node.
Delete element at rear in linked list
To delete the last element (rear) of a linked list, you need to traverse the list until you reach the
second-to-last node, update its next pointer to NULL, and free the memory allocated for the last
node. Here's how you can implement this operation in C:
void deleteAtRear()
{
struct Node *temp1, *temp2;
temp1=head;
if(head==NULL)
printf("List is empty\n");
else
{
if(head->next==NULL)
deleteAtFront();
else
{
while(temp1->next!=NULL)
{
temp2=temp1;
temp1=temp1->next;
}
temp2->next=NULL;
printf("Deleted Element is %d\n",temp1->data);
free(temp1);
}
}
}
In this code:
The function deleteAtRear, checks if the list is empty or if there is only one node, or more than
one node. If the list is empty, the function just outputs “List is empty”. If not, it traverses the list
until reaching the second-to-last node, frees the memory allocated for the last node, and updates
the next pointer of the second-to-last node to NULL.
Delete element positionally in linked list
To delete an element at a given position in a linked list, you need to traverse the list until you reach
the node before the desired position, adjust the pointers to skip the node to be deleted, and free the
memory allocated for that node. Here's how you can implement this operation in C:
void deleteAtPosition(int pos)
{
struct Node *temp1, *temp2;
int i=1;
temp1=head;
if(head==NULL)
printf("List is empty\n");
else
{
while(i<pos&&temp1!=NULL)
{
temp2=temp1;
temp1=temp1->next;
i++;
}
if(temp1!=NULL)
{
temp2->next=temp1->next;
printf("Deleted Element is %d\n",temp1->data);
free(temp1);
}
}
}
The deleteAtPosition function takes the position of the node to be deleted. It handles three cases:
deleting the head node if the given position is 1, deleting a node in the middle of the list, and it
will not delete any node if the input is out of range.
}
}
void deleteAtPosition(int pos)
{
struct Node *temp1, *temp2;
int i=1;
temp1=head;
if(head==NULL)
printf("List is empty\n");
else
{
while(i<pos&&temp1!=NULL)
{
temp2=temp1;
temp1=temp1->next;
i++;
}
if(temp1!=NULL)
{
temp2->next=temp1->next;
printf("Deleted Element is %d\n",temp1->data);
free(temp1);
}
}
}
int search(int x)
{
struct Node* t = head;
int i=1;
while (t!= NULL)
{
if(t->data==x)
return i;
t= t->next;
i++;
}
return(-1);
}
// Function to print the linked list
void printList()
{
struct Node* t = head;
while (t!= NULL)
{
printf("%d -> ", t->data);
t= t->next;
}
printf("NULL\n");
}
int main()
{
int ch,x,pos;
while(1)
{
printf("\n1.Insert Front\n2.Insert Rear\n3.Insert Positionally\n4.Delete Front \n5.Delete
Rear\n");
printf("6. Delete Positionally\n7. Search\n8.PrintList\n9.Exit\n");
printf("Enter your choice");
scanf("%d",&ch);
switch(ch)
{
case 1: printf("Enter element to insert");
scanf("%d",&x);
insertAtFront(x);
printList();
break;
case 2: printf("Enter element to insert");
scanf("%d",&x);
insertAtRear(x);
printList();
break;
case 3: printf("Enter element to insert and position to insert");
scanf("%d%d",&x,&pos);
insertAtPosition(x,pos);
printList();
break;
case 4: deleteAtFront();
printList();
break;
case 5: deleteAtRear();
printList();
break;
case 6: printf("Enter position to delete");
scanf("%d",&pos);
deleteAtPosition(pos);
printList();
break;
case 7:printf("Enter element to search");
scanf("%d",&x);
pos=search(x);
if(pos==-1)
printf("Element not fount\n");
else
printf("Found at %d position\n",pos);
case 8:printList();
break;
case 9: exit(0);
}
}
return 0;
}
OUTPUT
1.Insert Front
2.Insert Rear
3.Insert Positionally
4.Delete Front
5.Delete Rear
6. Delete Positionally
7. Search
8.PrintList
9.Exit
Enter your choice1
Enter element to insert20
20 -> NULL
1.Insert Front
2.Insert Rear
3.Insert Positionally
4.Delete Front
5.Delete Rear
6. Delete Positionally
7. Search
8.PrintList
9.Exit
Enter your choice2
Enter element to insert10
20 -> 10 -> NULL
1.Insert Front
2.Insert Rear
3.Insert Positionally
4.Delete Front
5.Delete Rear
6. Delete Positionally
7. Search
8.PrintList
9.Exit
Enter your choice3
Enter element to insert and position to insert25
2
20 -> 25 -> 10 -> NULL
1.Insert Front
2.Insert Rear
3.Insert Positionally
4.Delete Front
5.Delete Rear
6. Delete Positionally
7. Search
8.PrintList
9.Exit
Enter your choice7
Enter element to search10
Found at 3 position
20 -> 25 -> 10 -> NULL
1.Insert Front
2.Insert Rear
3.Insert Positionally
4.Delete Front
5.Delete Rear
6. Delete Positionally
7. Search
8.PrintList
9.Exit
Enter your choice6
Enter position to delete2
Deleted Element is 25
20 -> 10 -> NULL
1.Insert Front
2.Insert Rear
3.Insert Positionally
4.Delete Front
5.Delete Rear
6. Delete Positionally
7. Search
8.PrintList
9.Exit
Enter your choice5
Deleted Element is 10
20 -> NULL
1.Insert Front
2.Insert Rear
3.Insert Positionally
4.Delete Front
5.Delete Rear
6. Delete Positionally
7. Search
8.PrintList
9.Exit
Enter your choice4
Deleted Element is 20
NULL
1.Insert Front
2.Insert Rear
3.Insert Positionally
4.Delete Front
5.Delete Rear
6. Delete Positionally
7. Search
8.PrintList
9.Exit
Enter your choice9
llink rlink
(Holds a address (Holds a address
to the previous
Data to the previous
node) node)
Code Representation:
In programming languages, we use code structures to represent linked lists. Here's an example in
struct Node
{
int data;
struct Node* rlink;
struct Node* llink;
};
This code defines a structure called Node that represents a single node in the linked list.
The data field stores the information, and the rlink & llink pointer is a variable of type struct
Node*, meaning it can hold the address of the next and previous Node of the linked list.
Advantages of Doubly Linked Lists:
Doubly linked lists (DLLs) offer several advantages over singly linked lists due to their
bidirectional nature. Here are some of the key advantages:
Bidirectional Traversal: In DLLs, each node has pointers to both its previous and next nodes.
This allows for efficient traversal in both forward and backward directions. Singly-linked lists only
support forward traversal, requiring additional time and space to traverse in reverse.
Insertion and Deletion Operations: Insertion and deletion operations at the beginning and end
of the list are more efficient in DLLs compared to singly linked lists. This is because DLLs do not
require traversal from the head or tail to perform these operations. In contrast, singly linked lists
often require traversal to the insertion or deletion point, which can be inefficient.
Reverse Traversal: DLLs support efficient reverse traversal, which can be useful in various
scenarios such as printing data in reverse order or performing operations from the end of the list
to the beginning.
Memory Efficiency for Some Operations: While DLLs have an additional pointer per node
compared to singly linked lists, they can be more memory-efficient for certain operations. For
example, reversing a singly linked list requires additional space for stack or recursive calls, while
reversing a DLL can be done in place without extra memory.
Efficient Implementation of Algorithms: Certain algorithms, such as merge sort and quicksort,
benefit from bidirectional traversal offered by DLLs. These algorithms can efficiently manipulate
DLLs for sorting operations, improving overall performance.
Circular Lists: DLLs can easily be made circular by connecting the last node's next pointer to the
first node and the first node's previous pointer to the last node. This circular structure facilitates
operations such as rotation and can be used in applications like circular buffers and queues.
Overall, DLLs provide enhanced flexibility and efficiency for various operations compared
to singly linked lists, especially when bidirectional traversal and frequent insertion/deletion
operations are required. However, it's essential to consider the additional memory overhead
associated with maintaining the prev pointers in DLLs.
Operations on Doubly Linked Lists:
Insertion:
Insert at Beginning: Add a new node at the beginning of the list.
Insert at End: Append a new node at the end of the list.
Insert at Position: Insert a new node at a specific position in the list.
Deletion:
Delete from Beginning: Remove the first node from the list.
Delete from End: Remove the last node from the list.
Delete at Position: Remove a node from a specific position in the list.
Traversal: Traverse the list either forward or backward using the next and previous pointers of
each node.
Searching: Search for a specific value within the list by traversing it from the head or tail.
Operations on Doubly Linked Lists:
Insert at Beginning of the double linked list:
Inserting a node at the beginning of a doubly linked list involves creating a new node,
adjusting pointers to make it the new head, and updating the previous pointer of the old head (if it
exists) to point to the new node.
Let us assume a newNode as shown above. The newNode with data = 25 has to
be inserted at the beginning of the list.
The rlink pointer of the newNode is referenced to the head node and its llink
pointer is referenced to NULL.
The llink pointer of the head node is referenced to the newNode.
The newNode is then made as the head node.
void insertAtFront(int x)
{
struct Node* t;
t = (struct Node*)malloc(sizeof(struct Node));
t->data = x;
t->llink=NULL;
if(head!=NULL)
head->llink=t;
t->rlink=head;
head = t;
}
In this code:
We define a structure Node to represent each node of the doubly linked list. It contains an
integer data field and two pointers: rlink points to the next node, and llink points to the previous
node. The malloc() function is used to create a new node with the given data. The
insertAtBeginning function takes the data for the new node. It creates a new node and inserts it at
the beginning of the list by adjusting pointers appropriately.
Insert at End of the double linked list:
To insert a node at the end of a doubly linked list, you need to traverse the list until you
reach the last node, then adjust pointers to make the new node the new last node. If the list is empty,
the new node becomes the head of the list.
Now, let us assume a newNode as shown above. The newNode with data = 25 has to be
inserted at the end of the linked list. Make the rlink pointer of the last node to point to
the newNode. The rlink pointer of the newNode is referenced to NULL and its llink pointer
is made to point to the last node. Then, the newNode is made as the last node.
In this code:
We define a structure Node to represent each node of the doubly linked list. It contains an integer
data field and two pointers: rlink points to the next node, and llink points to the previous node. The
insertAtEnd function takes the data for the new node. It creates a new node and inserts it at the end
of the list by traversing the list until the last node is found, then adjusting pointers accordingly.
Insert at given position in the double linked list:
To insert a node at a given position in a doubly linked list, you need to traverse the list to find the
node at the desired position, then adjust pointers to insert the new node before or after it.
Now let us assume that a newNode with data = 25 has to be inserted at position 2 in the
linked list. Start traversing the list from head and move upto the position - 1. In this case, since we
want to insert at second position, you need to traverse till (2 - 1) = 1 st node. Once you reach the
position - 1 node, the next pointer of the newNode is set to the address contained by the next
pointer of the (position - 1) th node. Make the rlink pointer of the (position - 1) th node to point to
the newNode and the llink pointer of the newNode is pointed to the (position - 1) th node. Finally
the newNode next 's llink pointer must be made to point to the newNode.
Here's how you can implement this operation in C:
void insertAtPosition(int x,int pos)
{
struct Node *t, *temp, *temp2;
int c=1;
if(head==NULL || pos==1)
insertAtFront(x);
else
{
t = (struct Node*)malloc(sizeof(struct Node));
temp=head;
t->data=x;
while(c<pos-1 && temp->rlink!=NULL)
{
temp=temp->rlink;
c++;
}
if(temp->rlink==NULL)
insertAtRear(x);
else
{
temp2=temp->rlink;
t->rlink=temp2;
t->llink=temp;
temp2->llink=t;
temp->rlink=t;
}
}
}
void deleteAtRear()
{
struct Node *temp1, *temp2;
temp1=head;
if(head==NULL)
printf("List is empty\n");
else
{
if(head->rlink==NULL)
head=NULL;
else
{
while(temp1->rlink!=NULL)
temp1=temp1->rlink;
temp2=temp1->llink;
temp2->rlink=NULL;
printf("Deleted Element is %d\n",temp1->data);
}
free(temp1);
}
}
#include <stdio.h>
#include <stdlib.h>
// Define a structure for a node in the Double Linked List
struct Node {
int data; // Data of the node
struct Node* llink;
struct Node* rlink; // Pointer to the next node in the list
};
struct Node* head = NULL;
// Function to insert a new node at the beginning of the linked list
void insertAtFront(int x)
{
struct Node* t;
t = (struct Node*)malloc(sizeof(struct Node));
t->data = x;
t->llink=NULL;
if(head!=NULL)
head->llink=t;
t->rlink=head;
head = t;
}
void insertAtRear(int x)
{
struct Node* t, *temp;
t = (struct Node*)malloc(sizeof(struct Node));
temp=head;
t->data=x;
t->rlink=NULL;
if(head==NULL)
{
t->llink=NULL;
head=t;
}
else
{
while(temp->rlink!=NULL)
temp=temp->rlink;
t->llink=temp;
temp->rlink=t;
}
}
void insertAtPosition(int x,int pos)
{
struct Node *t, *temp, *temp2;
int c=1;
if(head==NULL || pos==1)
insertAtFront(x);
else
{
t = (struct Node*)malloc(sizeof(struct Node));
temp=head;
t->data=x;
while(c<pos-1 && temp->rlink!=NULL)
{
temp=temp->rlink;
c++;
}
if(temp->rlink==NULL)
insertAtRear(x);
else
{
temp2=temp->rlink;
t->rlink=temp2;
t->llink=temp;
temp2->llink=t;
temp->rlink=t;
}
}
}
void deleteAtFront()
{
struct Node *temp;
temp=head;
if(head==NULL)
printf("List is empty\n");
else
{
if(head->rlink==NULL)
head=NULL;
else
{
head=head->rlink;
head->llink=NULL;
}
printf("Deleted Element is %d\n",temp->data);
free(temp);
}
}
void deleteAtRear()
{
struct Node *temp1, *temp2;
temp1=head;
if(head==NULL)
printf("List is empty\n");
else
{
if(head->rlink==NULL)
head=NULL;
else
{
while(temp1->rlink!=NULL)
temp1=temp1->rlink;
temp2=temp1->llink;
temp2->rlink=NULL;
printf("Deleted Element is %d\n",temp1->data);
}
free(temp1);
}
}
void deleteAtPosition(int pos)
{
struct Node *temp1, *temp2;
int c=1;
temp1=head;
if(head==NULL)
printf("List is empty\n");
else
{
if (pos==1)
deleteAtFront();
else
{
while(c<pos-1&&temp1->rlink!=NULL)
{
temp1=temp1->rlink;
c++;
}
if(temp1->rlink!=NULL)
{
temp2=temp1->rlink;
temp1->rlink=temp2->rlink;
temp2->rlink->llink=temp1;
printf("Deleted Element is %d\n",temp2->data);
free(temp2);
}
}
}
}
int search(int x)
{
struct Node* t = head;
int c=1;
while (t!= NULL)
{
if(t->data==x)
return c;
t= t->rlink;
c++;
}
return(-1);
}
// Function to print the linked list
void printListForward()
{
struct Node* t = head;
while (t!= NULL)
{
printf("%d -> ", t->data);
t= t->rlink;
}
printf("NULL\n");
}
void printListBackward()
{
struct Node* t = head;
while (t->rlink!= NULL)
t= t->rlink;
while(t!=NULL)
{
printf("%d -> ", t->data);
t= t->llink;
}
printf("NULL\n");
}
int main()
{
int ch,x,pos;
while(1)
{
printf("\n1.Insert Front\n2.Insert Rear\n3.Insert Positionally\n4.Delete Front \n5.Delete
Rear\n");
printf("6. Delete Positionally\n7.
Search\n8.PrintListForward\n9.PrintListBackward\n10.Exit\n");
printf("Enter your choice");
scanf("%d",&ch);
switch(ch)
{
case 1: printf("Enter element to insert");
scanf("%d",&x);
insertAtFront(x);
printListForward();
break;
case 2: printf("Enter element to insert");
scanf("%d",&x);
insertAtRear(x);
printListForward();
break;
case 3: printf("Enter element to insert and position to insert");
scanf("%d%d",&x,&pos);
insertAtPosition(x,pos);
printListForward();
break;
case 4: deleteAtFront();
printListForward();
break;
case 5: deleteAtRear();
printListForward();
break;
case 6: printf("Enter position to delete");
scanf("%d",&pos);
deleteAtPosition(pos);
printListForward();
break;
case 7:printf("Enter element to search");
scanf("%d",&x);
pos=search(x);
if(pos==-1)
printf("Element not fount\n");
else
printf("Found at %d position\n",pos);
case 8:printListForward();
break;
case 9:printf("\n Backward List Traversing\n");
printListBackward();
break;
case 10: exit(0);
}
}
return 0;
}
Comparing Arrays and Linked Lists
Arrays and linked lists are fundamental data structures used to store collections of elements. Both
have distinct advantages and disadvantages based on their structure, performance, and use cases.
Below are detailed notes comparing these two data structures across various aspects:
1. Structure
Arrays:
o Fixed Size: Arrays have a fixed size that must be defined at the time of creation.
o Contiguous Memory: Elements are stored in contiguous memory locations.
o Indexing: Direct indexing allows O(1) time complexity for accessing elements.
Linked Lists:
o Dynamic Size: Linked lists can grow and shrink dynamically.
o Non-contiguous Memory: Elements (nodes) are stored in non-contiguous memory
locations, with each node containing a reference (or pointer) to the next node.
o No Direct Indexing: Accessing elements requires traversal from the head, resulting
in O(n) time complexity for accessing elements.
2. Insertion and Deletion
Arrays:
o Insertion: Inserting an element at a specific position requires shifting elements to
the right, resulting in O(n) time complexity in the worst case.
o Deletion: Deleting an element requires shifting elements to the left, also resulting
in O(n) time complexity in the worst case.
Linked Lists:
o Insertion: Inserting an element at the beginning (or end, if tail pointer is
maintained) can be done in O(1) time. Insertion at a specific position requires
traversal, resulting in O(n) time complexity.
o Deletion: Deleting an element (given a reference to the node) can be done in O(1)
time. However, finding the node to delete requires O(n) time complexity in the
worst case.
3. Memory Usage
Arrays:
o Static Allocation: Memory is allocated at the time of array creation. This can lead
to either wasted space (if the array is too large) or insufficient space (if the array is
too small).
o Overhead: Minimal overhead since only the elements are stored.
Linked Lists:
o Dynamic Allocation: Memory is allocated as needed. This is efficient in terms of
space utilization but can lead to overhead due to storing pointers (or references).
o Overhead: Each node requires additional memory for storing the pointer to the
next node (and previous node in the case of doubly linked lists).
4. Traversal
Arrays:
o Direct Access: Elements can be accessed directly using indices, allowing O(1) time
complexity for both read and write operations.
Linked Lists:
o Sequential Access: Elements must be accessed sequentially starting from the head
node, resulting in O(n) time complexity for traversal.
5. Flexibility
Arrays:
o Size Constraint: The size of an array is fixed after its creation. Resizing an array
involves creating a new array and copying elements, which is costly.
Linked Lists:
o Dynamic Size: Linked lists can easily grow and shrink in size, providing more
flexibility in handling varying amounts of data.
7. Complexity Summary
Operation Arrays Linked Lists
Arrays and linked lists each have their strengths and weaknesses. The choice between them
depends on the specific requirements of the application, such as the need for dynamic resizing,
access speed, and memory usage. Understanding these differences is crucial for selecting the
appropriate data structure for a given problem.
Applications of Linked Lists
Linked lists are a fundamental data structure with a wide range of applications in various areas of
computer science and programming. Their flexibility and dynamic nature make them suitable for
scenarios where arrays might not be the best fit. Below are detailed notes on the applications of
linked lists:
1. Implementation of Abstract Data Types (ADTs)
Stacks: Linked lists can be used to implement stacks. The push and pop operations can be
efficiently implemented by inserting and deleting nodes at the head of the list.
Queues: Linked lists are ideal for implementing queues. By maintaining pointers to both
the head and the tail, enqueue and dequeue operations can be performed in O(1) time.
Deques (Double-Ended Queues): Linked lists support efficient insertion and deletion at
both ends, making them suitable for implementing deques.
2. Dynamic Memory Allocation
Flexible Size: Linked lists are used in dynamic memory allocation schemes where the size
of the data structure changes frequently. Unlike arrays, linked lists do not require pre-
allocation of memory.
Memory Pools: They can be used to manage memory pools where blocks of memory need
to be allocated and freed dynamically.
3. Graph Representation
Adjacency Lists: Linked lists are commonly used to represent graphs. Each vertex in the
graph can be associated with a linked list that contains all the adjacent vertices. This
representation is efficient in terms of space and is suitable for sparse graphs.
4. Real-Time Applications
Music Player Playlists: Linked lists are used to manage playlists in music players where
songs can be dynamically added, removed, or moved.
Image Viewer: In image viewers, linked lists can be used to navigate through a series of
images, allowing the user to go forward and backward.
5. Operating Systems
Process Scheduling: Linked lists are used in operating systems for scheduling processes.
Ready queues and other scheduling structures can be efficiently implemented using linked
lists.
Memory Management: Free memory blocks can be managed using linked lists. This helps
in efficiently allocating and deallocating memory blocks.
6. Undo Functionality
Text Editors: Linked lists are used to implement undo functionality in text editors. Each
change can be represented as a node in the list, allowing the editor to traverse back through
the list to undo changes.
7. Hash Tables with Separate Chaining
Collision Handling: Linked lists are used in hash tables to handle collisions via separate
chaining. Each bucket of the hash table contains a linked list to store all elements that hash
to the same index.