C Programming Data Structures Singly Doubly And Circular Linked Lists Complete Guide
Understanding the Core Concepts of C Programming data structures Singly, Doubly, and Circular Linked Lists
C Programming Data Structures: Singly, Doubly, and Circular Linked Lists
Singly Linked List
A Singly Linked List is the simplest form of a linked list. In this structure, each node contains data fields and a reference (or link) to the next node in the sequence. This means traversal can only be done in one direction, from the head (first node) to the tail (last node).
Structure:
struct Node {
int data;
struct Node* next;
};
- Data: The value stored in the node.
- Next: A pointer to the next node in the list. The last node's
next
pointer isNULL
.
Operations:
- Insertion:
- At the beginning.
- At the end.
- At a specific position.
- Deletion:
- From the beginning.
- From the end.
- From a specific position.
- Search: Locate a node containing a specific value.
Advantages:
- Easy to implement.
- Insertion and deletion are efficient, especially at the beginning.
Disadvantages:
- Traversal is unidirectional.
- Deletion and insertion at arbitrary positions are inefficient compared to arrays.
Applications:
- Implementing stacks (using head node as top).
- Implementing queues (using pointers to both head and tail).
- Memory management in operating systems.
Doubly Linked List
A Doubly Linked List enhances the singly linked list by adding a pointer to the previous node in each node. This bidirectional capability allows traversal in both directions—forward from the head to the tail and backward from the tail to the head.
Structure:
struct Node {
int data;
struct Node* prev;
struct Node* next;
};
- Data: The value stored in the node.
- Prev: A pointer to the previous node.
- Next: A pointer to the next node.
Operations:
- Insertion:
- At the beginning.
- At the end.
- At a specific position.
- Deletion:
- From the beginning.
- From the end.
- From a specific position.
- Search: Locate a node containing a specific value.
Advantages:
- Traversal is bidirectional.
- Insertion and deletion are more efficient at arbitrary positions due to the
prev
pointer. - Easier to implement structures like a doubly ended queue (deque).
Disadvantages:
- Requires additional memory for the
prev
pointer. - More complex implementation compared to singly linked lists.
Applications:
- Implementing deques.
- Implementing hash tables due to ability to handle collisions efficiently with double linking.
- Graph traversal algorithms like DFS and BFS.
Circular Linked List
A Circular Linked List is a variation where the last node points back to the first node, forming a circular chain. This structure can be either singly or doubly linked. The circular nature allows for more efficient looping through the list without using NULL
pointer checks at the end.
Structure (Singly Circular):
struct Node {
int data;
struct Node* next;
};
- Data: The value stored in the node.
- Next: A pointer to the next node. The last node points to the head node.
Operations:
- Insertion:
- At the beginning.
- At the end.
- At a specific position.
- Deletion:
- From the beginning.
- From the end.
- From a specific position.
- Search: Locate a node containing a specific value.
Advantages:
- No
NULL
pointer at the end, simplifying circular loop logic. - Efficiently traversing nodes without needing special termination conditions.
- Useful for implementing structures needing circular behavior.
Disadvantages:
- Slightly more complex implementations for insertion and deletion.
- Additional consideration for edge cases like the empty list.
Applications:
- Implementing round-robin scheduling algorithms.
- Circular queues.
- Multiplayer games and simulations requiring a circular progression of players or processes.
Important Information:
- Node Management: Proper memory management is crucial in linked lists to prevent memory leaks. Use
malloc
to allocate memory for new nodes andfree
to deallocate memory when nodes are removed. - Time Complexity: Basic operations like insertion, deletion, and search have different time complexities depending on the type of linked list. Average time for search in a linked list is (O(n)).
- Memory Overhead: Each node in a doubly linked list requires additional memory for the
prev
pointer, which can be significant depending on the number of nodes.
Online Code run
Step-by-Step Guide: How to Implement C Programming data structures Singly, Doubly, and Circular Linked Lists
Singly Linked List
Step 1: Define the Node Structure
The node in a singly linked list contains a data field and a pointer to the next node.
#include <stdio.h>
#include <stdlib.h>
// Define the structure for a Node in a singly linked list
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));
if (!newNode) {
printf("Memory allocation failed\n");
return NULL;
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
Step 2: Insert Nodes into the List
Nodes can be inserted at the beginning, in the middle, or at the end of the list.
// Function to insert a Node at the beginning
void insertAtBeginning(struct Node** head, int data) {
struct Node* newNode = createNode(data);
if (*head)
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;
return;
}
struct Node* temp = *head;
while (temp->next != NULL)
temp = temp->next;
temp->next = newNode;
}
// Function to insert after a given Node
void insertAfterNode(struct Node* prevNode, int data) {
if (prevNode == NULL) {
printf("Previous node cannot be NULL\n");
return;
}
struct Node* newNode = createNode(data);
newNode->next = prevNode->next;
prevNode->next = newNode;
}
Step 3: Delete Nodes from the List
Nodes can be deleted based on their position or value.
// Function to delete the first Node
void deleteFirstNode(struct Node** head) {
if (*head == NULL) {
printf("List is empty\n");
return;
}
struct Node* temp = *head;
*head = (*head)->next;
free(temp);
}
// Function to delete the last Node
void deleteLastNode(struct Node** head) {
if (*head == NULL) {
printf("List is empty\n");
return;
}
struct Node* temp = *head;
struct Node* prev;
if (temp->next == NULL) {
*head = NULL;
} else {
while (temp->next != NULL) {
prev = temp;
temp = temp->next;
}
prev->next = NULL;
}
free(temp);
}
// Function to delete a Node by value
void deleteNodeByValue(struct Node** head, int value) {
if (*head == NULL) {
printf("List is empty\n");
return;
}
struct Node* temp = *head;
struct Node* prev = NULL;
// If the Data is at Head
if (temp->data == value) {
*head = temp->next;
free(temp);
return;
}
// Traversing the List for the Data
while (temp != NULL && temp->data != value) {
prev = temp;
temp = temp->next;
}
// If the Data is not in the List
if (temp == NULL) {
printf("Value %d not found\n", value);
return;
}
prev->next = temp->next;
free(temp);
}
Step 4: Display the List
Function to print all elements in the singly linked list.
// Function to display the List
void displayList(struct Node* head) {
while (head) {
printf("%d -> ", head->data);
head = head->next;
}
printf("NULL\n");
}
// Main function to test the operations
int main() {
struct Node* head = NULL;
// Insert nodes at the beginning
insertAtBeginning(&head, 5);
insertAtBeginning(&head, 10);
// Display list
printf("Initial List:\n");
displayList(head);
// Insert node at the end
insertAtEnd(&head, 15);
// Display list
printf("List after inserting 15 at the end:\n");
displayList(head);
// Insert node after a specific node
insertAfterNode(head->next, 20); // after second element
// Display list
printf("List after inserting 20 after the second element:\n");
displayList(head);
// Deleting a node by value
deleteNodeByValue(&head, 10);
// Display list
printf("List after deleting value 10:\n");
displayList(head);
// Deleting first and last node
deleteFirstNode(&head);
deleteLastNode(&head);
// Display list
printf("List after deleting first and last node:\n");
displayList(head);
return 0;
}
Doubly Linked List
Step 1: Define the Node Structure
In a doubly linked list, each node contains a data field and pointers to both the next and previous nodes.
#include <stdio.h>
#include <stdlib.h>
// Define the structure for a Node in a doubly linked list
struct DoublyNode {
int data;
struct DoublyNode* next;
struct DoublyNode* prev;
};
// Function to create a new Node
struct DoublyNode* createDoublyNode(int data) {
struct DoublyNode* newNode = (struct DoublyNode*)malloc(sizeof(struct DoublyNode));
newNode->data = data;
newNode->next = NULL;
newNode->prev = NULL;
return newNode;
}
Step 2: Insert Nodes
Insert nodes at the beginning, end, and after a specific node.
// Function to insert a Node at the beginning
void insertAtDoublyBeginning(struct DoublyNode** head, int data) {
struct DoublyNode* newNode = createDoublyNode(data);
newNode->next = *head;
if (*head != NULL)
(*head)->prev = newNode;
*head = newNode;
}
// Function to insert a Node at the end
void insertAtDoublyEnd(struct DoublyNode** head, int data) {
struct DoublyNode* newNode = createDoublyNode(data);
if (*head == NULL) {
*head = newNode;
return;
}
struct DoublyNode* temp = *head;
while (temp->next != NULL)
temp = temp->next;
temp->next = newNode;
newNode->prev = temp;
}
// Function to insert a Node after a specific Node
void insertAfterDoublyNode(struct DoublyNode* prevNode, int data) {
if (prevNode == NULL) {
printf("Previous node cannot be NULL\n");
return;
}
struct DoublyNode* newNode = createDoublyNode(data);
newNode->next = prevNode->next;
prevNode->next = newNode;
newNode->prev = prevNode;
if (newNode->next != NULL)
newNode->next->prev = newNode;
}
Step 3: Delete Nodes
Delete nodes from any position in the list.
// Function to delete a Node by value
void deleteDoublyNodeByValue(struct DoublyNode** head, int value) {
if (*head == NULL) {
printf("List is empty\n");
return;
}
struct DoublyNode* temp = *head;
// Find the Node with the specified value
while (temp->data != value && temp->next != NULL)
temp = temp->next;
// If the Node is not found
if (temp->data != value) {
printf("Value %d not found\n", value);
return;
}
// If the Node is the head
if (temp->prev == NULL)
*head = temp->next;
else
temp->prev->next = temp->next;
// If the Node is not the last Node
if (temp->next != NULL)
temp->next->prev = temp->prev;
free(temp);
}
Step 4: Display the List
Display the doubly linked list in both forward and backward directions.
// Function to display the List in forward direction
void displayDoublyForward(struct DoublyNode* head) {
while (head) {
printf("%d <-> ", head->data);
head = head->next;
}
printf("NULL\n");
}
// Function to display the List in reverse direction
void displayDoublyBackward(struct DoublyNode* tail) {
while (tail) {
printf("%d <-> ", tail->data);
tail = tail->prev;
}
printf("NULL\n");
}
// Main function to test the operations
int main() {
struct DoublyNode* head = NULL;
// Insert nodes at the beginning
insertAtDoublyBeginning(&head, 10);
insertAtDoublyBeginning(&head, 20);
// Insert node at the end
insertAtDoublyEnd(&head, 30);
// Insert node after a specific node
insertAfterDoublyNode(head->next, 40); // after second element
// Display list in forward and reverse directions
printf("List in forward order:\n");
displayDoublyForward(head);
struct DoublyNode* tail = head;
while (tail->next != NULL)
tail = tail->next;
printf("List in reverse order:\n");
displayDoublyBackward(tail);
// Deleting a node by value
deleteDoublyNodeByValue(&head, 20);
// Display list
printf("List after deleting value 20:\n");
displayDoublyForward(head);
return 0;
}
Circular Linked List
Step 1: Define the Node Structure
Here, each node contains a data field and a pointer to the next node, but the last node points back to the head node.
#include <stdio.h>
#include <stdlib.h>
// Define the structure for a Node in a circular linked list
struct CircularNode {
int data;
struct CircularNode* next;
};
// Function to create a new Node
struct CircularNode* createCircularNode(int data) {
struct CircularNode* newNode = (struct CircularNode*)malloc(sizeof(struct CircularNode));
newNode->data = data;
newNode->next = NULL;
return newNode;
}
Step 2: Insert Nodes
Insert nodes at the beginning or end of the list.
// Function to insert a Node at the beginning
void insertAtCircularBeginning(struct CircularNode** head, int data) {
struct CircularNode* newNode = createCircularNode(data);
if (*head == NULL) {
*head = newNode;
newNode->next = *head;
return;
}
struct CircularNode* 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 insertAtCircularEnd(struct CircularNode** head, int data) {
struct CircularNode* newNode = createCircularNode(data);
if (*head == NULL) {
*head = newNode;
newNode->next = *head;
return;
}
struct CircularNode* temp = *head;
while (temp->next != *head)
temp = temp->next;
temp->next = newNode;
newNode->next = *head;
}
Step 3: Delete Nodes
Handle deletion of nodes in a circular linked list.
// Function to delete a Node by value
void deleteCircularNodeByValue(struct CircularNode** head, int value) {
if (*head == NULL) {
printf("List is empty\n");
return;
}
struct CircularNode* current = *head, * prev = NULL;
if ((current->next == *head) && (current->data == value)) { // Only one node in the list
free(current);
*head = NULL;
return;
}
// Check if it's the head node
if (current->data == value) {
// Find the last node
while (current->next != *head)
current = current->next;
current->next = (*head)->next;
free(*head);
*head = current->next;
return;
}
// Traverse the list for the value
while ((current->next)->data != value && current->next != *head)
current = current->next;
// If value was not found in the list
if ((current->next)->data != value) {
printf("Value %d not found\n", value);
return;
}
// Unlink the node to be deleted
prev = current->next;
current->next = prev->next;
free(prev);
}
Step 4: Display the List
Display the circular linked list.
Login to post a comment.