There are a few flaws with linked lists the way we have it:
In order to solve these problems, we will introduce another type of linked list called a doubly linked list. A doubly linked list is a type of linked list, where, instead of having each node only know the location of the next node, it also knows the location of the previous one.
Below is a doubly linked list with three nodes:
The above image depicts a doubly linked list. Now instead of each node containing two things, they each contain three things. Like before there is the data element and the next pointer which tells us where the next node in the list is.
Now there is also another pointer called "prev". This pointer stores the location of the previous node in the list. Now that we have a link to the previous node at each item in the list, we can easily go backwards through the list.
Normal linked lists (called "singly linked lists") only store the location of the first node in the list, the head. Doubly linked lists normally store the location of the first node and the last node, which is often called "tail". This allows us to go directly to the end of the linked list.
To make a singly-linked list into a doubly linked list, there are a few changes we have to make. First we have to add the previous link into the Node declaration:
private class Node {
Type data;
Node next;
Node prev;
}
We then have to add a reference to the end of the list to the List object's member variables:
private Node head;
private Node tail;
// ...
This makes our list doubly linked, since now each node has two links, the next and the prev. Every function that deals with the linked list must now account for both links. So if we add a node, we have to setup both links appropriately.
Doubly linked lists solve the problem of adding nodes to the end, which we can now do directly, and the problem of looping through the list in reverse, which can now be done easily using the prev links.
Inserting nodes at the beginning of a doubly linked list is much the same as with a singly linked list. The only difference is that now, we must account for the prev pointer as well as the tail of the list:
Below is this algorithm implemented in Java:
// add a new value at the start
public void addStart(Type value) {
Node newNode = new Node();
newNode.data = value;
newNode.next = head;
newNode.prev = null;
if (head == null) {
tail = newNode;
} else {
head.prev = newNode;
}
head = newNode;
}
Now, to insert at the end of the list, we know longer have to go through the whole thing just to find the end. Now we can go directly to the end and add in nodes the same way we do at the start:
Notice that this is the same basic algorithm as adding at the front, except we flip next/prev and head/tail.
Below is this algorithm implemented in Java:
// add a new value to the end
public void addEnd(Type value) {
Node newNode = new Node();
newNode.data = value;
newNode.prev = tail;
newNode.next = null;
// set node before to point to new node, or head
if (tail == null) {
head = newNode;
} else {
tail.next = newNode;
}
tail = newNode;
}
Copyright © 2024 Ian Finlayson | Licensed under a Creative Commons BY-NC-SA 4.0 License.