Data structures — Doubly linked lists

Coccagerman
3 min readAug 31, 2021

Doubly linked lists are very similar to singly linked lists, a type of data structure that store values in the form of a list. Within the list each value is considered a node, and each node is connected with the following value in the list (or null in case the element is the last in the list) through a pointer. The first element of the list is considered the head, and the last element is considered the tail. Like with arrays, the length property is defined as the number of elements the list contains.

The difference between doubly and singly linked lists, is that doubly linked lists have its nodes connected through pointers with both the previous and the next value. While singly linked lists only connect its nodes with the next value.

These double pointers approach allows doubly linked lists to perform better with certain methods compared to singly linked lists, but at a cost of consuming more memory (with doubly linked lists we need to store two pointers instead of one).

The big O of doubly linked lists methods is the following:

  • Insertion — O(1)
  • Removal — O(1)
  • Search — O(n)
  • Access — O(n)

Like any data structure, different methods are implemented with it in order to operate over the data. The most common ones include: push, pop, unshift, shift, get, set, insert, remove and reverse.

A full implementation of a doubly linked list might look a bit like this:

class Node{
constructor(val){
this.val = val;
this.next = null;
this.prev = null;
}
}
class DoublyLinkedList {
constructor(){
this.head = null;
this.tail = null;
this.length = 0;
}
push(val){
var newNode = new Node(val);
if(this.length === 0){
this.head = newNode;
this.tail = newNode;
} else {
this.tail.next = newNode;
newNode.prev = this.tail;
this.tail = newNode;
}
this.length++;
return this;
}
pop(){
if(!this.head) return undefined;
var poppedNode = this.tail;
if(this.length === 1){
this.head = null;
this.tail = null;
} else {
this.tail = poppedNode.prev;
this.tail.next = null;
poppedNode.prev = null;
}
this.length--;
return poppedNode;
}
shift(){
if(this.length === 0) return undefined;
var oldHead = this.head;
if(this.length === 1){
this.head = null;
this.tail = null;
}else{
this.head = oldHead.next;
this.head.prev = null;
oldHead.next = null;
}
this.length--;
return oldHead;
}
unshift(val){
var newNode = new Node(val);
if(this.length === 0) {
this.head = newNode;
this.tail = newNode;
} else {
this.head.prev = newNode;
newNode.next = this.head;
this.head = newNode;
}
this.length++;
return this;
}
get(index){
if(index < 0 || index >= this.length) return null;
var count, current;
if(index <= this.length/2){
count = 0;
current = this.head;
while(count !== index){
current = current.next;
count++;
}
} else {
count = this.length - 1;
current = this.tail;
while(count !== index){
current = current.prev;
count--;
}
}
return current;
}
set(index, val){
var foundNode = this.get(index);
if(foundNode != null){
foundNode.val = val;
return true;
}
return false;
}
insert(index, val){
if(index < 0 || index > this.length) return false;
if(index === 0) return !!this.unshift(val);
if(index === this.length) return !!this.push(val);
var newNode = new Node(val);
var beforeNode = this.get(index-1);
var afterNode = beforeNode.next;
beforeNode.next = newNode, newNode.prev = beforeNode;
newNode.next = afterNode, afterNode.prev = newNode;
this.length++;
return true;
}
}

--

--