Linked List
Linked List
• Linked List is a linear data structure, in which elements are not stored
at a contiguous location, rather they are linked using pointers.
• Linked List forms a series of connected nodes, where each node
stores the data and the address of the next node.
• Node Structure: A node in a linked list typically consists of two
components:
Data: It holds the actual value or data associated with the node.
Next Pointer: It stores the memory address (reference) of the next
node in the sequence.
Head and Tail: The linked list is accessed through the head node,
which points to the first node in the list. The last node in the list
points to NULL or nullptr, indicating the end of the list. This node is
known as the tail node.
Why linked list data structure needed?
• 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.
• In a system, if we maintain a sorted list of IDs in an array id[] = [1000,
1010, 1050, 2000, 2040].
• If we want to insert a new ID 1005, then to maintain the sorted order,
we have to move all the elements after 1000 (excluding 1000).
• Deletion is also expensive with arrays until unless some special
techniques are used.
• For example, to delete 1010 in id[], everything after 1010 has to be
moved due to this so much work is being done which affects the
efficiency of the code.
Types of Linked Lists
• Single-linked list
• Double linked list
• Circular linked list
Single-linked list
• In a singly linked list, each node contains a reference to the next node
in the sequence. Traversing a singly linked list is done in a forward
direction.
Double linked list
• In a doubly linked list, each node contains references to both the next
and previous nodes. This allows for traversal in both forward and
backward directions, but it requires additional memory for the
backward reference.
Circular Linked List
• In a circular linked list, the last node points back to the head node,
creating a circular structure. It can be either singly or doubly linked.
Operations on Linked Lists
• Insertion: Adding a new node to a linked list involves adjusting the
pointers of the existing nodes to maintain the proper sequence.
Insertion can be performed at the beginning, end, or any position
within the list
• Deletion: Removing a node from a linked list requires adjusting the
pointers of the neighboring nodes to bridge the gap left by the
deleted node. Deletion can be performed at the beginning, end, or
any position within the list.
• Searching: Searching for a specific value in a linked list involves
traversing the list from the head node until the value is found or the
end of the list is reached
Advantages
• Dynamic Size: Linked lists can grow or shrink dynamically, as memory
allocation is done at runtime.
• Insertion and Deletion: Adding or removing elements from a linked
list is efficient, especially for large lists.
• Flexibility: Linked lists can be easily reorganized and modified
without requiring a contiguous block of memory.
Disadvantages
• Random Access: Unlike arrays, linked lists do not allow direct access
to elements by index. Traversal is required to reach a specific node.
• Extra Memory: Linked lists require additional memory for storing the
pointers, compared to arrays.
Singly Linked List
• A singly linked list is a linear data structure in which the elements are
not stored in contiguous memory locations and each element is
connected only to its next element using a pointer.
Given a Linked List, the task is to insert a new node in this given Linked
List at the following positions:
• At the front of the linked list
• After a given node.
• At the end of the linked list.
How to Insert a Node at the Front/Beginning of
Linked List
To insert a node at the start/beginning/front of a Linked List, we need
to:
• Make the first node of Linked List linked to the new node
• Remove the head from the original first node of Linked List
• Make the new node as the Head of the Linked List.
void push(struct Node* head_ref, int new_data)
{
/* 1. allocate node */
struct Node* new_node
= (struct Node*)malloc(sizeof(struct Node));
/* 2. put in the data */
new_node->data = new_data;
/* 3. Make next of new node as head */
new_node->next = head_ref;
/* 4. move the head to point to the new node */
head_ref = new_node;
}
Complexity Analysis:
• Time Complexity: O(1), We have a pointer to the head and we can
directly attach a node and change the pointer. So the Time
complexity of inserting a node at the head position is O(1) as it does a
constant amount of work.
• Auxiliary Space: O(1)
How to Insert a Node after a Given Node in Linked
List
• To insert a node after a given node in a Linked List, we need to:
• Check if the given node exists or not.
• If it do not exists,
• terminate the process.
• If the given node exists,
• Make the element to be inserted as a new node
• Change the next pointer of given node to the new node
• Now shift the next pointer of new node to the original next pointer of given node
void insertAfter(struct Node* prev_node, int new_data)
{
/*1. check if the given prev_node is NULL */
if (prev_node == NULL) {
printf("the given previous node cannot be NULL");
return;
}
/* 2. allocate new node */
struct Node* new_node
= (struct Node*)malloc(sizeof(struct Node));
/* 3. put in the data */
new_node->data = new_data;
/* 4. Make next of new node as next of prev_node */
new_node->next = prev_node->next;
/* 5. move the next of prev_node as new_node */
prev_node->next = new_node;
}
Complexity Analysis:
• Time complexity: O(1), since prev_node is already given as argument
in a method, no need to iterate over list to find prev_node
• Auxiliary Space: O(1) since using constant space to modify pointers
How to Insert a Node at the End of Linked
List
To insert a node at the end of a Linked List, we need to:
• Go to the last node of the Linked List
• Change the next pointer of last node from NULL to the new node
• Make the next pointer of new node as NULL to show the end of Linked
List
/* Given a reference (pointer to pointer) to the head of a list and an int,
appends a new node at the end */
void append(struct Node* head_ref, int new_data)
{
/* 1. allocate node */
struct Node* new_node
= (struct Node*)malloc(sizeof(struct Node));
struct Node* last = head_ref; /* used in step 5*/
/* 2. put in the data */
new_node->data = new_data;
/* 3. This new node is going to be the last node, so
make next of it as NULL*/
new_node->next = NULL;
/* 4. If the Linked List is empty, then make the new
* node as head */
if (head_ref == NULL) {
head_ref = new_node;
return;
}
/* 5. Else traverse till the last node */
while (last->next != NULL)
last = last->next;
/* 6. Change the next of last node */
last->next = new_node;
return;
}
Complexity Analysis:
• Time complexity: O(N), where N is the number of nodes in the linked
list. Since there is a loop from head to end, the function does O(n)
work.
• This method can also be optimized to work in O(1) by keeping an extra
pointer to the tail of the linked list/
• Auxiliary Space: O(1)
Search an element in a Linked List
• Initialize a node pointer, current = head.
• Do following while current is not NULL
• If the current value (i.e., current->key) is equal to the key being searched
return true.
• Otherwise, move to the next node (current = current->next).
• If the key is not found, return false
bool search(struct Node* head, int x)
{
struct Node* current = head; // Initialize current
while (current != NULL) {
if (current->key == x)
return true;
current = current->next;
}
return false;
}
Delete from a Linked List:-
You can delete an element in a list from:
• Beginning
• End
• Middle
Beginning
Point head to the next node i.e. second node
• temp = head
• head = head->next
• free(temp); or delete temp;
Delete from End:
Point head to the previous element i.e. last second
element Change next pointer to null
struct node *end = head;
struct node *prev = NULL;
while(end->next)
{ prev = end;
end = end->next;
}
prev->next = NULL;
Make sure to free unused memory
free(end); or delete end;
Delete from given position:
• Keeps track of pointer before node to delete and
pointer to node to delete
temp = head;
prev = head;
if(position == 0)
{head = head->next;
free(temp)}
else{
for(int i = 0; i < position; i++)
{
prev = temp;
if(prev == NULL) // position was greater than number of nodes in the
list
break;
temp = temp->next;
if (i == position - 1 && temp)
{ prev->next = temp->next;
free(temp); }
}
Time Complexity:
• Best Case: O(1) if given position is 1
• Average & Worst Case: O(N) where N is the length of the linked list.
This is because in the worst case, we need to traverse the entire
linked list to find the node to be deleted.
• Auxiliary Space: O(1) as we are only using a constant amount of extra
space for temporary variables and pointers, regardless of the size of
the linked list.
Doubly linked List
• A doubly linked list (DLL) is a special type of linked list in which each
node contains a pointer to the previous node as well as the next node
of the linked list.
Advantages of Doubly Linked List over the singly
linked list:
• A DLL can be traversed in both forward and backward directions.
• The delete operation in DLL is more efficient if a pointer to the node
to be deleted is given.
• We can quickly insert a new node before a given node.
• In a singly linked list, to delete a node, a pointer to the previous node
is needed. To get this previous node, sometimes the list is traversed
Disadvantages of Doubly Linked List over the
singly linked list:
• Every node of DLL Requires extra space for a previous pointer.
• All operations require an extra pointer previous to be maintained.
Applications
• It is used by web browsers for backward and forward navigation of
web pages
• LRU ( Least Recently Used ) / MRU ( Most Recently Used ) Cache are
constructed using Doubly Linked Lists.
• Used by various applications to maintain undo and redo
functionalities.
Adding Elements
A node can be inserted in a Doubly Linked List in four ways:
• At the front of the DLL.
• In between two nodes
• After a given node.
• Before a given node.
• At the end of the DLL.
Add a node at the front in a Doubly Linked
List:
• Firstly, allocate a new node (say new_node).
• Now put the required data in the new node.
• Make the next of new_node point to the current head of the doubly
linked list.
• Make the previous of the current head point to new_node.
• Lastly, point head to new_node.
void push(struct Node *head_ref, int new_data)
{
// 1. allocate node
struct Node* new_node
= (struct Node*)malloc(sizeof(struct Node));
// 2. put in the data
new_node->data = new_data;
// 3. Make next of new node as head and previous as NULL
new_node->next = head_ref;
new_node->prev = NULL;
// 4. change prev of head node to new node
if (head_ref) != NULL)
head_ref->prev = new_node;
// 5. move the head to point to the new node
head_ref = new_node;
}
Add a node after a given node in a Doubly Linked
List:
• Firstly create a new node (say new_node).
• Now insert the data in the new node.
• Point the next of new_node to the next of prev_node.
• Point the next of prev_node to new_node.
• Point the previous of new_node to prev_node.
• Change the pointer of the new node’s previous pointer to new_node.
void insertAfter(struct Node* prev_node, int new_data)
{
// Check if the given prev_node is NULL
if (prev_node == NULL) {
printf("the given previous node cannot be NULL");
return;
}
// 1. allocate new node
struct Node* new_node
= (struct Node*)malloc(sizeof(struct Node));
// 2. put in the data
new_node->data = new_data;
// 3. Make next of new node as next of prev_node
new_node->next = prev_node->next;
// 4. Make the next of prev_node as new_node
prev_node->next = new_node;
// 5. Make prev_node as previous of new_node
new_node->prev = prev_node;
// 6. Change previous of new_node's next node
if (new_node->next != NULL)
new_node->next->prev = new_node;
}
• Time Complexity: O(1)
Auxiliary Space: O(1)
Add a node at the end in a Doubly Linked
List:
• Create a new node (say new_node).
• Put the value in the new node.
• Make the next pointer of new_node as null.
• If the list is empty, make new_node as the head.
• Otherwise, travel to the end of the linked list.
• Now make the next pointer of last node point to new_node.
• Change the previous pointer of new_node to the last node of the list.
void append(struct Node *head_ref, int new_data)
{
// 1. allocate node
struct Node* new_node
= (struct Node*)malloc(sizeof(struct Node));
struct Node* last = head_ref; /* used in step 5*/
// 2. put in the data
new_node->data = new_data;
// 3. This new node is going to be the last node, so
// make next of it as NULL
new_node->next = NULL;
// 4. If the Linked List is empty, then make the new
// node as head
if (head_ref == NULL) {
new_node->prev = NULL;
head_ref = new_node;
return;
}
// 5. Else traverse till the last node
while (last->next != NULL)
last = last->next;
// 6. Change the next of last node
last->next = new_node;
// 7. Make last node as previous of new node
new_node->prev = last;
return;
}
Time Complexity: O(n)
Auxiliary Space: O(1)
Deletion in Doubly Linked List
• If the node to be deleted is the head node then make the next node
as head.
• If a node is deleted, connect the next and previous node of the
deleted node.
Deletion of the intermediate node.
void deleteNode(struct Node * head_ref, struct Node *del)
{
/* base case */
if (head_ref == NULL || del == NULL)
return;
/* If node to be deleted is head node */
if (head_ref == del)
head_ref = del->next;
/* Change next only if node to be deleted is NOT the last node */
if (del->next != NULL)
del->next->prev = del->prev;
/* Change prev only if node to be deleted is NOT the first node */
if (del->prev != NULL)
del->prev->next = del->next;
/* Finally, free the memory occupied by del*/
free(del);
return;
}
Circular Linked List
• The circular linked list is a linked list where all nodes are connected to
form a circle. In a circular linked list, the first node and the last node
are connected to each other which forms a circle. There is no NULL at
the end.
• Circular singly linked list: In a circular Singly linked list, the last node
of the list contains a pointer to the first node of the list. We traverse
the circular singly linked list until we reach the same node where we
started.
• Circular Doubly linked list: Circular Doubly Linked List has properties
of both doubly linked list and circular linked list in which two
consecutive elements are linked or connected by the previous and
next pointer and the last node points to the first node by the next
pointer and also the first node points to the last node by the previous
pointer.