What is a Doubly Linked List?
A doubly linked list is a data structure where each node contains:
- Data: The value stored in the node.
- Next: A pointer (reference) to the next node in the sequence.
- Prev: A pointer (reference) to the previous node in the sequence.
In a doubly linked list:
- The first node’s
Prevpointer isnull, as there’s no node before it. - The last node’s
Nextpointer isnull, indicating the end of the list.
This structure allows traversal in both directions (forward and backward), making some operations more efficient compared to singly linked lists.
The Technical View
-
Time Complexity:
- Access: (O(n)) (you must traverse the list).
- Insertion/Deletion:
- At the head or tail: (O(1)).
- At a specific position: (O(n)).
-
Space Complexity:
- (O(n)), where (n) is the number of nodes.
- Additional space is required for the
Prevpointers.
A Fifth-Grader's Summary
Think of a doubly linked list as a two-way train where each carriage has:
- A name tag (data).
- A pointer to the next carriage.
- A pointer to the previous carriage.
If you’re at one carriage, you can move forward to the next or backward to the previous without having to start from the engine (head).
Real-World Example
Imagine a browser’s history feature. You can navigate forward and backward through pages (nodes). The Next pointer helps you go forward, and the Prev pointer lets you go back.
Operations on a Doubly Linked List
1. Insert a Node at the Head
Problem: Add a new node with value 3 at the beginning of the list.
Code:
class DoublyLinkedListNode {
public int Data;
public DoublyLinkedListNode Next;
public DoublyLinkedListNode Prev;
public DoublyLinkedListNode(int data) {
Data = data;
Next = null;
Prev = null;
}
}
DoublyLinkedListNode InsertAtHead(DoublyLinkedListNode head, int data) {
DoublyLinkedListNode newNode = new DoublyLinkedListNode(data);
if (head != null) {
head.Prev = newNode;
}
newNode.Next = head;
return newNode;
}
Initial List: 5 <-> 10 <-> 15 <-> null
Steps:
- Create a new node with data
3. - Set the new node’s
Nextto the current head (5). - Update the current head’s
Prevpointer to the new node. - Update the head to the new node.
Updated List: 3 <-> 5 <-> 10 <-> 15 <-> null
2. Insert a Node at the Tail
Problem: Add a new node with value 20 at the end of the list.
Code:
DoublyLinkedListNode InsertAtTail(DoublyLinkedListNode head, int data) {
DoublyLinkedListNode newNode = new DoublyLinkedListNode(data);
if (head == null) return newNode;
DoublyLinkedListNode current = head;
while (current.Next != null) {
current = current.Next;
}
current.Next = newNode;
newNode.Prev = current;
return head;
}
Initial List: 5 <-> 10 <-> 15 <-> null
Steps:
- Create a new node with data
20. - Traverse to the last node (
15). - Set the last node’s
Nextto the new node. - Set the new node’s
Prevto the last node.
Updated List: 5 <-> 10 <-> 15 <-> 20 <-> null
3. Insert a Node at a Specific Position
Problem: Insert a node with value 12 at position 2 (0-based index).
Code:
DoublyLinkedListNode InsertAtPosition(DoublyLinkedListNode head, int data, int position) {
DoublyLinkedListNode newNode = new DoublyLinkedListNode(data);
if (position == 0) {
newNode.Next = head;
if (head != null) head.Prev = newNode;
return newNode;
}
DoublyLinkedListNode current = head;
while (position - 1 > 0) {
current = current.Next;
position--;
}
newNode.Next = current.Next;
newNode.Prev = current;
if (current.Next != null) current.Next.Prev = newNode;
current.Next = newNode;
return head;
}
Initial List: 5 <-> 10 <-> 15 <-> null
Steps:
- Create a new node with data
12. - Traverse to the node at position
1(10). - Update pointers:
- Set the new node’s
Nextto the node at position2(15). - Set the new node’s
Prevto the node at position1(10). - Update the
Prevpointer of the node at position2to the new node. - Update the
Nextpointer of the node at position1to the new node.
- Set the new node’s
Updated List: 5 <-> 10 <-> 12 <-> 15 <-> null
4. Delete a Node
Problem: Remove the node at position 1 (0-based index).
Code:
DoublyLinkedListNode DeleteNode(DoublyLinkedListNode head, int position) {
if (head == null) return null;
if (position == 0) {
head = head.Next;
if (head != null) head.Prev = null;
return head;
}
DoublyLinkedListNode current = head;
while (position - 1 > 0) {
current = current.Next;
position--;
}
current.Next = current.Next.Next;
if (current.Next != null) current.Next.Prev = current;
return head;
}
Initial List: 5 <-> 10 <-> 15 <-> 20 <-> null
Steps:
- Traverse to the node at position
0(5). - Skip the node at position
1by updating:- The
Nextpointer of5to15. - The
Prevpointer of15to5.
- The
Updated List: 5 <-> 15 <-> 20 <-> null
5. Reverse a Doubly Linked List
Problem: Reverse the order of nodes in the list.
Code:
DoublyLinkedListNode Reverse(DoublyLinkedListNode head) {
DoublyLinkedListNode current = head;
DoublyLinkedListNode temp = null;
while (current != null) {
temp = current.Prev;
current.Prev = current.Next;
current.Next = temp;
current = current.Prev;
}
return temp?.Prev;
}
Initial List: 5 <-> 10 <-> 15 <-> 20 <-> null
Steps:
- Start with the first node (
5). - Swap the
NextandPrevpointers for each node. - Continue until the end of the list.
- The new head is the last node (
20).
Updated List: 20 <-> 15 <-> 10 <-> 5 <-> null
Conclusion
Doubly linked lists are versatile and allow efficient traversal in both directions. They are particularly useful in scenarios where you frequently need to navigate forward and backward, like undo/redo operations in text editors or navigation in browsers.
Stay tuned for the next post in this series, where we’ll demystify circular linked lists!
Top comments (0)