In data structures, a linear data structure is one where elements are arranged sequentially, one after another. The most familiar example is an array, where elements are stored in contiguous memory locations. Arrays allow quick access to elements by their index, but they come with some limitations:
To overcome these problems, we use linked lists, a dynamic data structure that allows flexible memory usage and efficient insertions and deletions.
A linked list is made up of nodes. Each node contains two parts:
Unlike arrays, nodes in a linked list can be scattered anywhere in memory. The pointer connects them logically, forming a chain. This allows the list to grow or shrink dynamically as needed.
Think of a linked list like a treasure hunt: each clue (node) tells you where to find the next clue, rather than having all clues laid out in a row.
A singly linked list is the simplest form of linked list. Each node contains data and a pointer to the next node only. The list starts with a special pointer called the head, which points to the first node. The last node points to null, indicating the end of the list.
Each node points only to the next node, so traversal is unidirectional, from head to tail.
A doubly linked list extends the singly linked list by adding a second pointer in each node. This pointer points to the previous node, allowing traversal in both directions - forward and backward.
This bidirectional feature makes certain operations, like deletion or reverse traversal, more efficient.
A circular linked list is a variation where the last node points back to the first node, forming a loop. This can be implemented with singly or doubly linked lists.
In a singly circular linked list, the last node's next pointer points to the head node instead of null. In a doubly circular linked list, the last node's next points to the head, and the head's previous points to the last node.
This circular structure is useful for applications requiring continuous cycling through data, such as round-robin scheduling or buffering.
Linked lists support three fundamental operations:
These operations involve updating pointers carefully to maintain the list's structure.
graph TD Start[Start Operation] --> CheckType{Insertion or Deletion?} CheckType -->|Insertion| InsertPos{Position?} InsertPos -->|Beginning| InsertBegin[Update head pointer] InsertPos -->|End| InsertEnd[Traverse to last node, update pointer] InsertPos -->|Middle| InsertMiddle[Traverse to position, update pointers] CheckType -->|Deletion| DeletePos{Position?} DeletePos -->|Beginning| DeleteBegin[Update head pointer] DeletePos -->|End| DeleteEnd[Traverse to second last node, update pointer] DeletePos -->|Middle| DeleteMiddle[Traverse to position, update pointers] InsertBegin --> EndOp[End] InsertEnd --> EndOp InsertMiddle --> EndOp DeleteBegin --> EndOp DeleteEnd --> EndOp DeleteMiddle --> EndOpStep 1: Create a new node with data = 5.
Step 2: Set the new node's next pointer to the current head node (which contains 10).
Step 3: Update the head pointer to point to the new node.
Result: The list now starts with 5, followed by 10, 20, 30.
Step 1: Traverse to the node with data 30.
Step 2: Update the previous node's (20) next pointer to point to 30's next node (40).
Step 3: Update the next node's (40) previous pointer to point to 30's previous node (20).
Step 4: Remove the node 30 from memory (optional in some languages).
Result: The list becomes [10 <> 20 <> 40].
Step 1: Start from the head node (10).
Step 2: Print the data of the current node.
Step 3: Move to the next node.
Step 4: Repeat steps 2 and 3 until you reach the head node again.
Note: Use a do-while loop to ensure the head node is processed before checking the condition.
Step 1: Memory for array = number of elements x size of each element
\( M_{array} = 1000 \times 4 = 4000 \text{ bytes} \)
Step 2: Memory for singly linked list = number of nodes x (data size + pointer size)
\( M_{list} = 1000 \times (4 + 8) = 1000 \times 12 = 12000 \text{ bytes} \)
Step 3: Cost for array memory:
\( \text{Cost}_{array} = 4000 \times 0.05 = Rs.200 \)
Step 4: Cost for linked list memory:
\( \text{Cost}_{list} = 12000 \times 0.05 = Rs.600 \)
Conclusion: Arrays use less memory and cost less, but linked lists offer dynamic size and easier insertions/deletions.
Enqueue Operation:
Step 1: Create a new node with the data to be enqueued.
Step 2: If the queue is empty (head is null), set head and tail to the new node.
Step 3: Otherwise, set the current tail's next pointer to the new node.
Step 4: Update the tail pointer to the new node.
Dequeue Operation:
Step 1: Check if the queue is empty (head is null). If yes, report underflow.
Step 2: Store the data from the head node.
Step 3: Update the head pointer to the next node.
Step 4: If head becomes null, also set tail to null.
Step 5: Return the stored data.
When to use: When performing insertion or deletion operations in linked lists.
When to use: To simplify code logic in linked list operations.
do-while loop to ensure all nodes are visited exactly once without infinite loops. When to use: When traversing circular linked lists.
When to use: When accessing elements by position in linked lists.
When to use: Before implementing linked list algorithms in exams or coding.
| Feature | Singly Linked List | Doubly Linked List | Circular Linked List |
|---|---|---|---|
| Node Structure | Data + Next pointer | Data + Prev + Next pointers | Last node points to first node |
| Traversal | One direction (forward) | Two directions (forward & backward) | Continuous loop traversal |
| Insertion/Deletion | Easy at head, harder elsewhere | Easier anywhere due to prev pointer | Similar to singly/doubly but circular |
| Memory Usage | Less (one pointer) | More (two pointers) | Same as singly/doubly but circular |
| Use Cases | Simple lists, stacks | Complex lists, undo operations | Round-robin, buffering |
Progress tracking is paywalled — subscribe to mark subtopics as understood and save your streak.
Go to practice →