Doubly Linked List in Data Structure
Doubly Linked List in Data Structure
A doubly linked list is a type of data structure where each element, known as a node,
holds data and two references: one pointing to the next node and one pointing to the
previous node.
This two-way linking allows for movement in both directions through the list,
making it more versatile than a singly linked list which only allows movement in one
direction.
Node:
Data: The actual information held within the node, which could be numbers, strings,
or any other data type.
Next Pointer: A reference to the next node in the list, which helps in traversing the
list forward.
Prev Pointer: A reference to the previous node in the list, which facilitates backward
traversal.
Work Process
Head and Tail: The list has two special nodes, the head and the tail. The head points
to the first element of the list, while the tail points to the last element. These pointers
enhance operations such as adding or removing elements at the ends of the list.
Traversal: You can start from the head to move forward through the list or from the
tail to move backward, making it versatile for various operations that require scanning
the list in either direction.
Insertions and Deletions: Adding or removing nodes can be done efficiently at any
point in the list. When inserting or deleting a node, pointers from adjacent nodes are
updated to maintain the list’s integrity without needing to shift elements as in
contiguous data structures like array
Doubly Linked List Operations
Operations on doubly linked lists in data structures are more versatile compared to singly
linked lists due to their two-way linking of nodes:
1. Insertion
Inserting nodes into a doubly linked list can be done in several ways, each using the ability to
access nodes from both directions:
At the Beginning: Add a new node at the start of the list. Set the new node's next
pointer to the current head of the list, and update the prev pointer of the existing head
to point back to the new node. Update the head pointer to the new node.
At the End: Add a new node at the end. Traverse to the last node, set the next pointer
of the last node to the new node, and set the prev pointer of the new node to the last
node. Update the tail pointer if maintained.
After a Given Node: To insert a node after a given node, adjust the next pointer of
the new node to the next pointer of the given node, update the next pointer of the
given node to the new node, and set the prev pointer of the node that follows the new
node (if any) to the new node.
2. Deletion
Removing nodes requires adjustment of pointers from both the preceding and succeeding
nodes:
From the Beginning: Remove the head node by updating the head to the second node
and setting the prev pointer of the new head to null.
From the End: Remove the tail node by setting the next pointer of the second last
node to null and updating the tail to this second last node.
A Specific Node: Disconnect the node by adjusting the next pointer of the preceding
node to point to the node after the target node, and adjust the prev pointer of the
succeeding node likewise.
3. Search
Searching involves traversing through the list either from the head or the tail, depending on
proximity and potentially the direction of traversal that may optimize the search:
Search by Value: Start from the head (or tail) and traverse through the next (or prev)
pointers to find the node containing the desired value.
4. Traversal
Backward Traversal: Start from the tail (if available) and move through each node
using the prev pointers.
5. Update
Updating the value of a node can be performed directly once the node is accessed, without
any specific need for traversal if the node reference is already known.
Traversal O(n)
Easier Insertion and Deletion: Nodes can be added or removed from both ends and
the middle of the list without needing to traverse the entire list, especially if the tail
pointer is maintained.
Dynamic Size: The size of the list can increase or decrease dynamically, which is
efficient for memory usage since it allocates space only as needed.
Disadvantages of Doubly Linked Lists
Increased Memory Usage: Each node requires extra memory for an additional
pointer (previous pointer), which can be significant in memory-constrained
environments.
Complexity: Managing two pointers (next and prev) per node increases the
complexity of the operations, making the code more prone to errors such as memory
leaks and pointer corruption.
Doubly linked lists find a wide range of applications in software development and system
design due to their ability to efficiently add, remove, and access elements from both ends:
Navigation Systems: Doubly linked lists are ideal for applications where users need
to navigate both forward and backward, such as web browsers or document viewers.
Music Players: In media playback software, doubly linked lists can manage playlists
where users might want to go to the next or previous track. This allows for seamless
navigation through the playlist.
Gaming: In gaming, doubly linked lists can be used to manage various game states or
the inventory of items that players can cycle through both forward and backward.
Implementing Other Data Structures: Doubly linked lists are also used to
implement more complex data structures like deque (double-ended queue) which
requires adding and removing items from both ends efficiently.
Less memory per node (one More memory per node (two pointers
Memory Usage
pointer per node). per node).
1. Circular Singly Linked List: Here, each node has only one pointer i.e. next pointer.
The next pointer of the last node of the list points to the first node of the list creating a
circular structure. We can transverse only in one direction in a circular manner in this.
2. Doubly Circular Linked List: We saw there are two pointers, prev and next in
the doubly linked lists in data structures. In this type also each node has two
pointers, prevpoints to the previous node, and the next points to the next node. Here,
in addition to the last node's next pointer storing the address of the first node, even the
first node's prev pointer will store the address of the last node thus forming a circular
structure.
Adding a node to a circular linked list is similar to adding a node to a linked list. The only
extra work is to connect the last node with the first node. In the above given circular linked
list, Insertion can be done in three ways:
2. store the address of the current first node in the new Node (i.e. pointing the
new Node to the current first node)
3. point the last node to new Node (i.e making new Node as head)
Insertion at a specific position/in between the nodes: Suppose we want to insert a node
with value 6 after the head node with value 1 in the given circular linked list. The following
three steps to accomplish this operation:
1. store the address of the head node to the next of new Node (making new Node
the last node)
2. point the current last node to new Node
3. make new Node as the last node
2. Deletion
In the above given circular linked list, deletion can also be done in three ways:
1. If it's the only node: Follow the given two steps procedure
1. free the memory occupied by the node
2. store NULL in the last
2. If it's the last node: Follow the given four steps procedure
1. find the node before the last node (let it be temp)
2. store the address of the node next to the last node in temp
3. free the memory of the last
4. make temp the last node
3. If it's any other node: Follow the given four steps procedure
1. travel to the node to be deleted (here we are deleting node two)
2. Let the node before node two be temp
3. store the address of the node next to two in temp
4. free the memory of two
3. Traversal
We can access each element of the circular linked list starting from
the head node until we reach back to it.
Complexity Analysis of Circular Linked List Operations
1. Any node can be a starting point. We can traverse the whole list by starting from any
point. We just need to stop when the first visited node is visited again.
2. Useful for the implementation of a queue. Unlike this implementation, we don’t need
to maintain two-pointers for the front and rear if we use a circular linked list. We can
maintain a pointer to the last inserted node and the front can always be obtained as
next of last.
3. Circular Doubly Linked Lists are used for the implementation of advanced data
structures like the Fibonacci Heap.
4. Implementing a circular linked list can be relatively easy compared to other more
complex data structures like trees or graphs.