Singly Linked List
A singly linked list is the simplest type of linked list, where each node contains data and a pointer to the next node of the same data type. The general syntax of singly linked list is given below
where,
- Head: Points to the first node in the list.
- Data: Stores the value or content of the node.
- Next pointer: Points to the next node in the list.
In C programming: Code
#include <stdio.h> #include <stdlib.h>struct Node { int data; // Data part struct Node *next; // Pointer to the next node }; |
Node Structure of the Singly Liked list
Address | Offset | Data | Next Pointer |
---|---|---|---|
350 | +0 | Data (10) | |
358 | +8 | Next (pointer) | 380 (address of Node 2) |
380 | +0 | Data (20) | |
388 | +8 | Next (pointer) | 700(address of Node 3) |
700 | +0 | Data (30) | |
708 | +8 | Next (pointer) | NULL (end of list) |
Single Node Explaination in Singly Liked list
For each node in memory, two parts are allocated:
- Data Field:
- Stores the actual data (e.g., an integer).
- The size depends on the data type. For an integer (
int
), it typically requires 4 bytes (depending on the system architecture).
- Pointer Field (
next
):- Stores the address of the next node.
- Since this is a pointer to a
struct Node
, it typically requires 4 bytes on a 32-bit system or 8 bytes on a 64-bit system.
- Ofsets field: offset indicates the relative position of a particular field within the node’s memory block.
- When accessing the
data
of a node, the program usescurrent->data
, which internally means accessing the base address plus an offset of 0. - When accessing the
next
pointer, the program usescurrent->next
, which means accessing the base address plus an offset of 4 bytes.
- When accessing the
- Node Size: Each node typically occupies
(size of data) + (size of "Next" pointer)
bytes in memory.
Byte by Byte Memory layout
Here is the updated table with an additional column that assigns nodes based on the address range for 32 bit system where the address is 4 byte in size
Updated Byte-by-Byte Memory Layout Table (32-bit Addresses)
Address in Decimal | Address in Binary (32-bit) | Data in Binary (Byte by byte) | Description | Node |
---|---|---|---|---|
350 | 00000000 00000000 00000001 01011110 | 00001010 | First byte of data (10) | Node 1 |
351 | 00000000 00000000 00000001 01011111 | 00000000 | Second byte of data (10) | Node 1 |
352 | 00000000 00000000 00000001 01100000 | 00000000 | Third byte of data (10) | Node 1 |
353 | 00000000 00000000 00000001 01100001 | 00000000 | Fourth byte of data (10) | Node 1 |
354 | 00000000 00000000 00000001 01100010 | 10111100 | First byte of pointer (380) | Node 1 |
355 | 00000000 00000000 00000001 01100011 | 00010111 | Second byte of pointer (380) | Node 1 |
356 | 00000000 00000000 00000001 01100100 | 00000000 | Third byte of pointer (380) | Node 1 |
357 | 00000000 00000000 00000001 01100101 | 00000000 | Fourth byte of pointer (380) | Node 1 |
380 | 00000000 00000000 00000001 01111000 | 00010100 | First byte of data (20) | Node 2 |
381 | 00000000 00000000 00000001 01111001 | 00000000 | Second byte of data (20) | Node 2 |
382 | 00000000 00000000 00000001 01111010 | 00000000 | Third byte of data (20) | Node 2 |
383 | 00000000 00000000 00000001 01111011 | 00000000 | Fourth byte of data (20) | Node 2 |
384 | 00000000 00000000 00000001 10000000 | 00000010 | First byte of pointer (700) | Node 2 |
385 | 00000000 00000000 00000001 10000001 | 10111000 | Second byte of pointer (700) | Node 2 |
386 | 00000000 00000000 00000001 10000010 | 00000000 | Third byte of pointer (700) | Node 2 |
387 | 00000000 00000000 00000001 10000011 | 00000000 | Fourth byte of pointer (700) | Node 2 |
700 | 00000000 00000000 00000010 10111000 | 00011110 | First byte of data (30) | Node 3 |
701 | 00000000 00000000 00000010 10111001 | 00000000 | Second byte of data (30) | Node 3 |
702 | 00000000 00000000 00000010 10111010 | 00000000 | Third byte of data (30) | Node 3 |
703 | 00000000 00000000 00000010 10111011 | 00000000 | Fourth byte of data (30) | Node 3 |
704 | 00000000 00000000 00000010 11000000 | 00000000 | NULL pointer (first bytes) | Node 3 |
705 | 00000000 00000000 00000010 11000001 | 00000000 | NULL pointer (all 2nd bytes) | Node 3 |
706 | 00000000 00000000 00000010 11000010 | 00000000 | NULL pointer (all 3rd bytes) | Node 3 |
707 | 00000000 00000000 00000010 11000011 | 00000000 | NULL pointer (all 4th bytes) | Node 3 |
Characteristics of Singly linked list
Here are the key characteristics of a singly linked list:
- Sequential Access: Nodes are connected in sequence, with each node pointing to the next.
- Unidirectional: Traversal is only possible from the head to the end; there’s no backward link.
- Dynamic Size: It can easily grow or shrink in size by adding or removing nodes.
- Non-Contiguous Memory: Nodes are stored in non-contiguous memory locations, linked via pointers.
- Head Pointer: The head pointer keeps track of the first node; if the head is lost, the list becomes inaccessible.
- End Node: The last node points to null, indicating the end of the list.
- Flexible Insertion/Deletion: Nodes can be added or removed without reorganizing the entire list, unlike arrays.
Singly-linked list – [Create, Access, Traversal]
The linked list has a head pointer that points to the first node. This head pointer doesn’t store any actual data of the list; it just stores the memory address of the first node.
In the example you provided, the head pointer stores the address 350, which is where the first node (10) is located. So, head pointer is simply a reference that helps us locate the start of the list. The first node itself has its own data (10 in this case) and a pointer to the next node (address 4900).
Example code:
#include <stdio.h> #include <stdlib.h>// Define the linked list 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 traverse a linked list void traverseLinkedList(struct Node* head) { struct Node* current = head;// Traverse until the end of the linked list while (current != NULL) { printf(“%d -> “, current->data); current = current->next; } printf(“NULL\n”); } int main() { // Link the nodes together // Traverse and print the linked list return 0; |
Output: 1 → 2 → 3 → Null.
Key Operations in the singly linked list
Theoretical understanding is very easy, but To understand the implementation of key operations on a singly linked list, you must understand pointers, arrays, and structures. Insertion, deletion, and traversal are the three key operations in a singly linked list
1. Insertion Operations
At the Beginning: To make the new node the first item, set its next pointer to the current head and then update head to be this new node.
Example: If the list is 20 → 30, and you add 10 at the beginning:
- 10.next points to 20.
- The list becomes: 10 → 20 → 30.
Steps:
- Allocate memory for the new node.
- Assign the desired value to the data field of the new node.
- Update the
next
pointer of the new node to point to the current head of the list. - Move the head pointer to the newly created node, making it the first node in the list.
Implementation code
struct node *newNode;
// Allocate memory for the new node // Assign data to the new node // Point the new node to the current head of the list // Update head to point to the new node |
Important: The operation newNode->next = head;
uses the old value of head
, and after head = newNode;
, the pointer now refers to the new node.
II. At the End: To add a node at the end, traverse the list to find the last node (where next is Null), then set its next pointer to the new node.
Example: If the list is 10 → 20, and you add 30 at the end:
- 20.next points to 30
- 30.next points to Null
- The list becomes: 10 → 20 → 30
Steps:
- Allocate memory for a new node
- Store the data.
- Traverse till the last node.
- Change next of the last node to the newly created node
// Create and allocate memory for a new node struct node *newNode; newNode = (struct node *)malloc(sizeof(struct node));// Store data in the newly created node and initialize next pointer to NULL newNode->data = 4; newNode->next = NULL;// Traverse the list to find the last node struct node *current = head; while (current->next != NULL) { current = current->next; }// Update the next pointer of the last node to point to the new node current->next = newNode; |
iii. In the Middle: To insert a node in the middle, find the nodes before and after the insertion point. Set the next pointer of the node before to the new node and the new node’s next pointer to the node after.
Example: If the list is 10 → 30 and you add 20 in between:
- 10.next points to 20.
- 20.next points to 30.
- The list becomes: 10 → 20 → 30.
Steps:
- Allocate memory and store data for the new node.
- Navigate to the node just before the position where the new node needs to be inserted.
- Update the next pointers to insert a new node in between existing nodes.
// Create and allocate memory for a new node struct node *newNode; newNode = (struct node *)malloc(sizeof(struct node));// Store data in the newly created node newNode->data = 4;// Traverse the list to the specified position struct node *current = head; for (int i = 2; i < position; i++) { if (current->next != NULL) { current = current->next; } }// Insert the new node at the specified position newNode->next = current->next; current->next = newNode; |
2. Deletion Operations
Deletion at the Beginning: To delete the first node in the list, we simply need to update the head to point to the second node, and then free the memory of the original head node.
Steps:
- Update
head
to point to the next node.
Code:
head = head->next; |
Deletion at the End: To delete a node at the end, we need to traverse the list to find the second-last node and update its next
pointer to NULL
. Then, we can free the memory of the last node.
Steps:
- Traverse to the second-last node in the list.
- Update the
next
pointer of the second-last node toNULL
.
Code:
struct node *temp, *current;
// If the list is empty, there’s nothing to delete // If the list has only one node // Traverse to the second-last node // Store the last node in temp // Update the second-last node’s next pointer to NULL // Free the last node |
The while
loop helps us find the second-last node to properly disconnect the last node.
Deletion in the Middle
Steps
- Traverse to the node just before the one to be deleted.
- Store the node to be deleted in a temporary pointer.
- Update the
next
pointer of the current node to skip the node being deleted. - Free the memory of the node to be deleted.
Code:
struct node *temp, *current;
// If the list is empty, there’s nothing to delete // Traverse to the node just before the one to be deleted // Store the node to be deleted in temp // Update current’s next to skip the deleted node // Free the deleted node |
Note: In this example, position
starts from 1
(i.e., the head node is at position 1
). We traverse to position - 1
to ensure we’re at the correct spot to change pointers safely.
3. Search for an element in a linked list
To search for an element in a linked list using a loop, you can follow these steps:
- Set the head node as the current node.
- Traverse the list using a loop, continuing until the current node becomes
NULL
(since the last node’snext
pointer isNULL
). - During each iteration, compare the key of the current node with the target value. If they match, return
true
; if no match is found throughout the loop, returnfalse
.
// Function to search for a specific value in the linked list bool searchNode(struct Node** head_ref, int key) { // Start with the head node as the current node struct Node* current = *head_ref;// Traverse through the linked list until the end is reached while (current != NULL) { // If the current node’s data matches the key, return true if (current->data == key) { return true; } // Move to the next node current = current->next; }// Return false if the key is not found return false; } |
Time and space complexity in a singly linked list
Here is a table that combines both time and space complexities for the operations on a singly linked list:
Operation | Time Complexity | Space Complexity |
---|---|---|
Insert | O(1) at head, O(n) at tail or middle | O(1) |
Delete | O(1) at head, O(n) at tail or middle | O(1) |
Search | O(n) | O(1) |
Traverse | O(n) | O(1) |
The time complexities vary based on the position of insertion or deletion, while the space complexity for these operations is O(1), since no extra space is needed beyond a few pointers.
Note: In a Singly Linked List, each node only has a next pointer, allowing traversal in one direction (forward). This simpler structure makes insertion and deletion more straightforward than in a doubly linked list but limits backward traversal.