What is a Doubly Linked List?
A doubly linked list is a type of linked list where each node contains data and two pointers – one pointing to the next node and another pointing to the previous node. Think of it as a chain where each link is connected to both the link before it and the link after it, allowing you to move in both directions!
Unlike a regular (singly) linked list where you can only move forward, a doubly linked list lets you traverse both forward and backward through the data.
Real-Life Examples
1. Music Playlist
Your music app’s playlist is like a doubly linked list! You can:
- Move to the next song (forward traversal)
- Go back to the previous song (backward traversal)
- Add songs at the beginning, middle, or end
- Remove songs from anywhere in the playlist
2. Browser History
When you browse the internet:
- Forward button takes you to the next page you visited
- Back button takes you to the previous page
- You can navigate in both directions through your browsing history
3. Photo Gallery
In your phone’s photo gallery:
- Swipe right to see the next photo
- Swipe left to see the previous photo
- You can navigate through photos in both directions
4. Train Compartments
A train with connected compartments:
- You can walk forward through compartments
- You can walk backward through compartments
- Each compartment is connected to the one before and after it
Memory Utilization Compared to Arrays/Lists
Memory Structure Comparison
Array/List:
Memory: [10][20][30][40][50]
Continuous memory blocks
PythonDoubly Linked List:
Memory: [prev|10|next] -> [prev|20|next] -> [prev|30|next]
Scattered memory locations
PythonMemory Usage Analysis
Aspect | Array/List | Doubly Linked List |
---|---|---|
Memory per element | 4-8 bytes (just data) | 12-24 bytes (data + 2 pointers) |
Memory layout | Continuous | Scattered |
Memory efficiency | High | Lower (due to pointer overhead) |
Cache performance | Excellent | Poor |
Example: Storing 1000 integers
- Array: ~4KB (4 bytes × 1000)
- Doubly Linked List: ~12KB (12 bytes × 1000)
Pros and Cons
Advantages
- Bidirectional Traversal
- Can move forward and backward easily
- No need to restart from the beginning
- Efficient Insertion/Deletion
- O(1) time if you have the node reference
- No need to shift elements like in arrays
- Dynamic Size
- Can grow and shrink during runtime
- No need to declare size beforehand
- No Memory Waste
- Only allocates memory as needed
- No unused reserved space
Disadvantages
- Higher Memory Usage
- Extra memory for storing two pointers per node
- Approximately 3x more memory than arrays
- Poor Cache Performance
- Nodes scattered in memory
- CPU cache misses more frequent
- No Random Access
- Can’t directly access element at index i
- Must traverse from head or tail
- Complex Implementation
- More pointers to manage
- Higher chance of bugs
Complete Code Walkthrough
Node Structure
class Node:
def __init__(self, val):
self.val = val # Data stored in the node
self.next = None # Pointer to next node
self.prev = None # Pointer to previous node (key difference!)
PythonExplanation: Each node has three parts – the data and two pointers. This is what makes it “doubly” linked!
Basic Operations
1. Insert at Head
def insert_at_head(self, val):
new_node = Node(val)
if not self.head:
self.head = new_node # First node becomes head
else:
new_node.next = self.head # New node points to current head
self.head.prev = new_node # Current head's prev points to new node
self.head = new_node # Update head to new node
PythonHow it works: Like adding a new car to the front of a train – you need to connect it to the current first car and update what’s considered the “front.”
2. Insert at End (Append)
def append(self, val):
new_node = Node(val)
if not self.head:
self.head = new_node
else:
current = self.head
while current.next: # Find the last node
current = current.next
current.next = new_node # Connect last node to new node
new_node.prev = current # Connect new node back to last node
PythonHow it works: Like adding a new car to the end of a train – find the last car and connect the new car to it.
3. Insert at Position
def insert_at(self, val, position):
new_node = Node(val)
if position == 0:
self.insert_at_head(val)
return
current = self.head
count = 0
while current and count < position - 1:
current = current.next
count += 1
if current is None:
print("Position out of bounds")
return
new_node.next = current.next # Connect new node to next node
new_node.prev = current # Connect new node to current node
if current.next:
current.next.prev = new_node # Update next node's prev pointer
current.next = new_node # Connect current node to new node
PythonHow it works: Like inserting a new car between two cars in a train – you need to connect it to both the car before and after.
4. Traversal Operations
def traverse_forward(self):
current = self.head
while current:
print(current.val, end=" ")
current = current.next # Move forward
print()
def traverse_backward(self):
current = self.head
while current.next: # Go to the last node
current = current.next
while current:
print(current.val, end=" ")
current = current.prev # Move backward
print()
PythonHow it works: Forward traversal is like walking through train cars from front to back, while backward traversal is like walking from back to front.
When to Use Doubly Linked Lists
Use When:
- You need bidirectional traversal (music players, browsers)
- Frequent insertions/deletions in the middle
- Dynamic size requirements
- Undo/Redo functionality needed
Don’t Use When:
- Memory is limited (mobile apps, embedded systems)
- You need random access to elements
- Cache performance is critical
- Simple sequential processing is sufficient
Summary
Feature | Array | Singly Linked List | Doubly Linked List |
---|---|---|---|
Memory per element | 4-8 bytes | 8-16 bytes | 12-24 bytes |
Random access | O(1) | O(n) | O(n) |
Insertion at head | O(n) | O(1) | O(1) |
Insertion at end | O(1) | O(n) | O(n) |
Bidirectional traversal | ✅ | ❌ | ✅ |
Cache performance | Excellent | Poor | Poor |
Key Takeaway: Doubly linked lists are perfect when you need the flexibility to move in both directions and don’t mind the extra memory cost. They’re like having a two-way street instead of a one-way street – more versatile but requires more infrastructure!
Choose doubly linked lists when the benefits of bidirectional traversal outweigh the memory overhead for your specific use case.
For any changes to the article, kindly email at code@codeanddebug.in or contact us at +91-9712928220.