Doubly Linked List Operation
Doubly Linked List Operation
Doubly Linked List Operation
The doubly linked list data structure is a linked list made up of nodes with
two pointers pointing to the next and previous element.
I will assume that readers are comfortable with the basic singly linked list. If
not, I recommend reading about the singly linked list before proceeding.
Let’s get started shall we?
Train.
You can only directly access the next or previous node from any given
point/node.
Here is a visual representation of the doubly linked list.
Doubly Linked List Implementation
The only difference between a singly and doubly inked list is in our Node
class or object. Instead of having just a single pointer, we have two
pointers.
Before proceeding, I highly recommend, once again, to make sure that you
fully understand how a singly linked list works.
I will assume that you have a decent working knowledge of the singly
linked list.
In contrast to the singly linked list, our doubly linked list node will have two
pointers LITERALLY pointing to the next and previous node.
For all linked list implementations, we must have either a head and/or
a tail. I will mention this just in case. The head and tail node are the first
and last node of a linked list respectively.
So our Node container (in whatever language you program in), will have
the following attributes.
Data.
Next node.
Previous node.
Basic Operations
The basic operations that every linked list should support include the
following.
Insertion Operation
When inserting a node to the doubly linked list, we now have to worry about
two pointers, instead of one. As long you understand the fundamentals of
how nodes are linked together, this shouldn’t be too difficult.
If we have a linked list that has both a tail and a head, we gain the
advantage of being able to insert to the front and back of the list in O(1)
constant time!
For each insertion operation, we need to consider the three cases. These
three cases also need to be considered when removing data from the
doubly linked list.
1. Front insertion (inserting a head node).
The ultimately goal is to achieve the link shown in the diagram below. The
steps described above are one way to ensure that this happens. If you
figure out an alternative method to establishing the link, then by all means,
feel free to implement it your way.
Note also that in some languages such as Java, by not assigning a value,
the default value is null.
Front Insertion Pseudo Code
Here is the first pseudo code example. I have tried to write out all the logic
inside of a single method so that we don’t have to jump back and forth from
multiple method calls (at least until we understand the basics).
begin insertAtFront(data):
if head is null:
head = new Node(data);
// Tail and head cannot point at the same node
tail = null;
else:
if tail is null:
tail = new Node(data);
// Update references
// Head --> Tail
// Head <-- Tail
head.setNext(tail)
tail.setPrev(head)
else:
Node<T> prevHead = head;
Node<T> newHead = new Node(data);
// Update references
// newHead.next --> prevHead
// prevHead.prev <-- newHead
newHead.setNext(prevHead)
prevHead.setPrev(newHead)
head = newHead
end if else;
end if else;
increment size
end insertAtFront;
Copy
Back Insertion
The back insertion mirrors the front insertion.
o If head is empty
1. set C as the new head. Existence of head node indicates that there are
elements in the list (well, at least for this implementation).
o Otherwise, check if tail is null.
If tail is null
1. Set next node pointer of current head (B) to point to new node (C).
2. Set prev node pointer of new node (C) to point at current head (B).
Otherwise,
Comparing the two examples will help you wrap your mind around the
difference and solidify your working knowledge of the doubly linked list.
// Update references
// prevTail --> newTail
// prevTail <-- newTail
newTail.setPrev(prevTail)
prevTail.setNext(newTail)
tail = newTail
end if else;
end if else;
increment size
end insertAtBack;
Copy
Middle Insertion
In the middle insertion, we need to perform the following operations. While
walking through the middle insertion operation, please refer to the diagram
below.
Remove Operation
In contrast to the singly linked list, because our nodes have two pointers,
we have slightly more work to do. If you fully understand the insertion
operation in the doubly linked list, the remove operation is very similar.
There are three main cases when removing a node to the doubly linked list.
Cases where the node that we are deleting is the
1. Head.
2. Tail.
3. Neither the head or tail. In another word, the node to remove is somewhere
in the middle of the list.
1. Set head to null.
2. Set next node after the head (A) as the new head.
3. Update A‘s prev pointer value to null.
Removing Head Pseudo Code
begin removeFromFront():
if head is not null: {
nextNode = head.getNext()
// Set prev node of next to null if it exists
if nextNode is not null:
nextNode.setPrev(null)
end if;
// Remove current head
head = null;
head = nextNode;
decrement size
end if;
end removeFromFront;
Copy
Case Two: Removing Tail
Removing a tail node mirrors the head removal operation.
1. Set tail to null.
2. Set previous node (B) as the new tail.
3. Update B‘s next node pointer value to null.
In the case that we are removing the last element, we also need to make a
check if this is the last element. In our implementation, the tail cannot equal
the head. Therefore, if the head node does not equal null but
the tail is null, then we are removing the last element.
In this case, all we need to to is remove the head node but setting its
reference to null.
decrement size
end if;
end removeFromBack;
Copy
Case Three: Removing from the middle of the list.
This is probably the most complex case out of the three, because we now
have to work with three nodes:
1. Previous node.
2. Next node.
3. Node to remove.
Pseudo Code
Now that we have reviewed all the cases, let’s take a look at the pseudo
code for the entire remove operation.
Remove operation
begin remove(T dataToRemove):
currentNode = head
while current node is not null:
currentData = currentNode.data
if currentData equals dataToRemove:
call removeNode(currentNode)
end if;
currentNode = currentNode.next
end while;
end remove;
Copy
removeNode operation
begin removeNode(nodeToRemove):
prevNode = nodeToRemove.prev
nextNode = nodeToRemove.next
// Head node does not have a previous node
if prevNode is null:
head = null
head = nextNode
head.prev = null
else if nextNode is null:
// Tail does not have a next node
tail = null
tail = prevNode
tail.next = null
else:
// Is somewhere in the middle of the linked list
// Set current node to null
nodeToRemove = null
// connect the previous and next nodes together
prevNode.next = nextNode
nextNode.prev = prevNode
end if else statement;
decrement size
end removeNode;
Copy
I recommend playing around with the doubly linked list by adding the
aforementioned features such as insertion/removal by index. In the source
code, I have added these features for your reference.
Here is a list of possible features that you can add to your doubly linked list
to get more practice.
Batch inserting a list (adding a list to the linked list, as opposed to single
elements).
Update your insertAfter(data,
index) and removeAt(index) methods to calculate the fastest way of
traversing to the node at that index. E.g. If we are removing the second last
node in a list of five items, it will start looking from the tail.