A doubly linked list is a data structure consisting of a sequence of nodes, each containing a value and two references: one to the previous node and one to the next node. This allows for efficient traversal in both directions, making it useful for a variety of applications. In contrast to a singly linked list, which only has references to the next node, a doubly linked list can be traversed forwards and backwards without having to start from the beginning.
The first node in the list is called the head, and the last node is called the tail. A doubly linked list can be modified by adding or removing nodes from either end or in the middle. For example, a new node can be added to the end of the list by updating the tail node's "next" reference to point to the new node, and the new node's "prev" reference to point to the tail.
Doubly linked lists have many practical applications, such as implementing browser history, text editor undo/redo functionality, or a music playlist. However, they also require more memory than singly linked lists because each node has to store two references instead of one.
Visualization Of Doubly LinkedList
Here's a visual representation of a doubly linked list:
HEAD TAIL
↓ ↓
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│prev │ │prev │ │prev │ │prev │ │prev │ │prev │
│ - ├────►│ A ├────►│ B ├────►│ C ├────►│ D ├────►│ E │
│next │◄────┤next │◄────┤next │◄────┤next │◄────┤next │◄────┤next │
└─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘
In a doubly linked list, each node contains a value and two references: one to the previous node and one to the next node. The first node in the list is called the head, and the last node is called the tail.
In the figure above, we have a doubly linked list with five nodes, labeled A through E. The arrows show the references between the nodes. For example, node A has a reference to node B as its next node, and node B has a reference to node A as its previous node.
Note that the references allow for efficient traversal in both directions. For example, if we start at the head of the list and want to move to the next node, we can simply follow the "next" reference. If we want to move to the previous node, we can follow the "prev" reference.
Overall, a doubly linked list is a powerful data structure that can be used in a variety of applications, such as implementing a browser's back and forward buttons or a text editor's undo and redo functionality.
Syntax Of Doubly Linked List
In JavaScript, a doubly linked list can be implemented using a Node class and a LinkedList class. Here's an example of the syntax for the Node class:
class Node {
constructor(value) {
this.value = value;
this.next = null;
this.prev = null;
}
}
In JavaScript, a doubly linked list can be implemented using a Node class and a LinkedList class. Here's an example of the syntax for the Node class:
class Node {
constructor(value) {
this.value = value;
this.next = null;
this.prev = null;
}
}
The Node class defines a node in the doubly linked list, with a "value" property and two references: "next" points to the next node in the list, and "prev" points to the previous node.
Next, here's an example of the syntax for the LinkedList class:
class LinkedList {
constructor() {
this.head = null;
this.tail = null;
this.length = 0;
}
// add methods for adding and removing nodes, etc.
}
The LinkedList class defines the entire list, with a "head" property pointing to the first node in the list, a "tail" property pointing to the last node, and a "length" property keeping track of the number of nodes in the list. The class also includes methods for adding and removing nodes, as well as traversing the list.
These are just examples of the syntax for a doubly linked list in JavaScript. The actual implementation may vary depending on the specific use case and requirements.
Code Examples
Insertion Operation
Insertion operation in a doubly linked list adds a new node to the beginning of the list.
The
insertAtBeginning
method adds a new node to the beginning of the list. It takes a value as an argument and creates a new node with that value. If the list is empty, the new node becomes both the head and tail of the list. Otherwise, the new node is inserted at the beginning of the list by updating the "prev" reference of the current head node to point to the new node, and updating the "next" reference of the new node to point to the current head node. Finally, the head pointer is updated to point to the new node.class Node { constructor(value) { this.value = value; this.next = null; this.prev = null; } } class DoublyLinkedList { constructor() { this.head = null; this.tail = null; this.length = 0; } // Insert Node at Beginning insertAtBeginning(value) { const node = new Node(value); if (!this.head) { this.head = node; this.tail = node; } else { this.head.prev = node; node.next = this.head; this.head = node; } this.length++; return this; } // Display all the nodes displayForward() { let current = this.head; let result = ''; while (current !== null) { result += `${current.value} -> `; current = current.next; } result += 'null'; console.log(result); } }
In the above code, we define a Node class that represents a node in a doubly linked list, with a "value" property and two references: "next" points to the next node in the list, and "prev" points to the previous node.
We also define a DoublyLinkedList class that represents the entire list, with a "head" property pointing to the first node in the list, a "tail" property pointing to the last node, and a "length" property keeping track of the number of nodes in the list.
Example:
Let's assume we have an empty doubly linked list, and we want to add the value "apple" to the beginning of the list using the
insertAtBeginning
method.const list = new DoublyLinkedList(); list.insertAtBeginning("apple"); console.log(list); list.displayForward();
The output will be:
DoublyLinkedList { head: Node { value: 'apple', next: null, prev: null }, tail: Node { value: 'apple', next: null, prev: null }, length: 1 } banana -> apple -> null
In this example, we create a new instance of the DoublyLinkedList class and call the
insertAtBeginning
method with the value "apple". The method creates a new node with the value "apple" and sets it as both the head and tail of the list. Theconsole.log
statement outputs the list object, which contains the new node and its properties.Deletion Operation
Deletes an element at the beginning of the list. To delete the first node in the list, we simply update the head pointer to point to the second node in the list.
The
deleteFromBeginning
method removes the first node from the list. It returns the deleted node, or undefined if the list is empty. If the list has only one node, both the head and tail pointers are set to null. Otherwise, the head pointer is updated to point to the second node, and the "prev" reference of the new head node is set to null. Finally, the "next" reference of the deleted node is set to null.class Node { constructor(value) { this.value = value; this.next = null; this.prev = null; } } class DoublyLinkedList { constructor() { this.head = null; this.tail = null; this.length = 0; } // Insert Node at Beginning insertAtBeginning(value) { const node = new Node(value); if (!this.head) { this.head = node; this.tail = node; } else { this.head.prev = node; node.next = this.head; this.head = node; } this.length++; return this; } // Delete From Beginning deleteFromBeginning() { if (!this.head) { return undefined; } const deletedNode = this.head; if (this.length === 1) { this.head = null; this.tail = null; } else { this.head = this.head.next; this.head.prev = null; deletedNode.next = null; } this.length--; return deletedNode; } }
In the above code, we define a Node class that represents a node in a doubly linked list, with a "value" property and two references: "next" points to the next node in the list, and "prev" points to the previous node.
We also define a DoublyLinkedList class that represents the entire list, with a "head" property pointing to the first node in the list, a "tail" property pointing to the last node, and a "length" property keeping track of the number of nodes in the list.
Example:
Let's assume we have a doubly linked list with three nodes, and we want to remove the first node from the list using the
deleteFromBeginning
method.const list = new DoublyLinkedList(); list.insertAtBeginning("apple"); list.insertAtBeginning("banana"); list.insertAtBeginning("orange"); list.deleteFromBeginning(); console.log(list);
The output will be:
DoublyLinkedList { head: Node { value: 'banana', next: Node { value: 'apple', next: null, prev: [Circular] }, prev: null }, tail: Node { value: 'apple', next: null, prev: Node { value: 'banana', next: [Circular], prev: null } }, length: 2 }
In this example, we create a new instance of the DoublyLinkedList class and add three nodes to the list using the
insertAtBeginning
method. Then, we call thedeleteFromBeginning
method, which removes the first node "orange" from the list. Theconsole.log
statement outputs the updated list object, which contains the remaining nodes and their properties.Insert Last Operation
Insert Last operation in a doubly linked list adds a new node at the end of the list.
The
insertAtEnd
method adds a new node at the end of the list. It takes a value as a parameter and creates a new Node object with the given value. If the list is empty, the head and tail pointers are set to the new node. Otherwise, the next reference of the current tail node is set to the new node, and the prev reference of the new node is set to the current tail node. Finally, the tail pointer is updated to point to the new node.class Node { constructor(value) { this.value = value; this.next = null; this.prev = null; } } class DoublyLinkedList { constructor() { this.head = null; this.tail = null; this.length = 0; } // Insert at End insertAtEnd(value) { const newNode = new Node(value); if (!this.head) { this.head = newNode; this.tail = newNode; } else { this.tail.next = newNode; newNode.prev = this.tail; this.tail = newNode; } this.length++; return this; } }
In the above code, we define a Node class that represents a node in a doubly linked list, with a "value" property and two references: "next" points to the next node in the list, and "prev" points to the previous node.
We also define a DoublyLinkedList class that represents the entire list, with a "head" property pointing to the first node in the list, a "tail" property pointing to the last node, and a "length" property keeping track of the number of nodes in the list.
Example:
Let's assume we have a doubly linked list with two nodes, and we want to add a new node "grape" at the end of the list using the
insertAtEnd
method.const list = new DoublyLinkedList(); list.insertAtEnd("apple"); list.insertAtEnd("banana"); list.insertAtEnd("grape"); console.log(list);
The output will be:
DoublyLinkedList { head: Node { value: 'apple', next: Node { value: 'banana', next: [Node], prev: [Circular] }, prev: null }, tail: Node { value: 'grape', next: null, prev: Node { value: 'banana', next: [Circular], prev: [Node] } }, length: 3 }
In this example, we create a new instance of the DoublyLinkedList class and add two nodes to the list using the
insertAtEnd
method. Then, we call theinsertAtEnd
method again to add a new node "grape" at the end of the list. Theconsole.log
statement outputs the updated list object, which contains all three nodes and their properties.Delete Last Operation
Delete Last operation in a doubly linked list removes the last node from the list.
The
deleteLast
method removes the last node from the list. If the list is empty, it returns undefined. Otherwise, it sets the deletedNode variable to point to the current tail node, and updates the tail pointer to point to the previous node. If the list only had one node, both the head and tail pointers are set to null. Finally, the deleted node's next and prev references are set to null, and the length property is decremented by 1.class Node { constructor(value) { this.value = value; this.next = null; this.prev = null; } } class DoublyLinkedList { constructor() { this.head = null; this.tail = null; this.length = 0; } // Insert at End insertAtEnd(value) { const newNode = new Node(value); if (!this.head) { this.head = newNode; this.tail = newNode; } else { this.tail.next = newNode; newNode.prev = this.tail; this.tail = newNode; } this.length++; return this; } // Delete Last Node deleteLast() { if (!this.tail) { return undefined; } const deletedNode = this.tail; if (this.length === 1) { this.head = null; this.tail = null; } else { this.tail = deletedNode.prev; this.tail.next = null; deletedNode.prev = null; } this.length--; return deletedNode; } }
In the above code, we define a Node class that represents a node in a doubly linked list, with a "value" property and two references: "next" points to the next node in the list, and "prev" points to the previous node.
We also define a DoublyLinkedList class that represents the entire list, with a "head" property pointing to the first node in the list, a "tail" property pointing to the last node, and a "length" property keeping track of the number of nodes in the list.
Example:
Let's assume we have a doubly linked list with three nodes, and we want to remove the last node from the list using the
deleteLast
method.const list = new DoublyLinkedList(); list.insertAtEnd("apple"); list.insertAtEnd("banana"); list.insertAtEnd("grape"); list.deleteLast(); console.log(list);
The output will be:
DoublyLinkedList { head: Node { value: 'apple', next: Node { value: 'banana', next: null, prev: [Circular] }, prev: null }, tail: Node { value: 'banana', next: null, prev: Node { value: 'apple', next: [Circular], prev: null } }, length: 2 }
In this example, we create a new instance of the DoublyLinkedList class and add three nodes to the list using the
insertAtEnd
method. Then, we call thedeleteLast
method to remove the last node "grape" from the list. Theconsole.log
statement outputs the updated list object, which contains only the first two nodes and their properties.Insert After Operation
Insert After operation in a doubly linked list inserts a new node after a specific node in the list.
The
insertAfter
method inserts a new node with the given value after the node with the given key. If the key is not found in the list, it returns false. Otherwise, it creates a new node object, and iterates through the list to find the node with the given key. Once found, it updates thenext
andprev
references of the new node and the adjacent nodes to insert the new node into the list.class Node { constructor(value) { this.value = value; this.next = null; this.prev = null; } } class DoublyLinkedList { constructor() { this.head = null; this.tail = null; this.length = 0; } // Insert at End insertAtEnd(value) { const newNode = new Node(value); if (!this.head) { this.head = newNode; this.tail = newNode; } else { this.tail.next = newNode; newNode.prev = this.tail; this.tail = newNode; } this.length++; return this; } // Insert After Operation insertAfter(value, key) { const newNode = new Node(value); let currentNode = this.head; while (currentNode) { if (currentNode.value === key) { if (currentNode === this.tail) { this.tail = newNode; } else { currentNode.next.prev = newNode; newNode.next = currentNode.next; } currentNode.next = newNode; newNode.prev = currentNode; this.length++; return this; } currentNode = currentNode.next; } return false; } }
In the above code, we define a Node class that represents a node in a doubly linked list, with a "value" property and two references: "next" points to the next node in the list, and "prev" points to the previous node.
We also define a DoublyLinkedList class that represents the entire list, with a "head" property pointing to the first node in the list, a "tail" property pointing to the last node, and a "length" property keeping track of the number of nodes in the list.
Example:
Let's assume we have a doubly linked list with three nodes, and we want to insert a new node with the value "orange" after the node with the value "banana" in the list using the
insertAfter
method.const list = new DoublyLinkedList(); list.insertAtEnd("apple"); list.insertAtEnd("banana"); list.insertAtEnd("grape"); list.insertAfter("orange", "banana"); console.log(list);
The output will be:
DoublyLinkedList { head: Node { value: 'apple', next: Node { value: 'banana', next: [Node], prev: [Circular] }, prev: null }, tail: Node { value: 'grape', next: null, prev: Node { value: 'orange', next: [Circular], prev: [Node] } }, length: 4 }
In this example, we create a new instance of the DoublyLinkedList class and add three nodes to the list using the
insertAtEnd
method. Then, we call theinsertAfter
method to insert a new node with the value "orange" after the node with the value "banana". Theconsole.log
statement outputs the updated list object, which contains four nodes and their properties, including the newly inserted node with the value "orange".Delete Operation
Delete operation in a doubly linked list removes a node from the list based on a given key.
The
delete
method removes the node with the given key from the list. If the key is not found in the list, it returns false. Otherwise, it iterates through the list to find the node with the given key. Once found, it updates thenext
andprev
references of the adjacent nodes to remove the node from the list.class Node { constructor(value) { this.value = value; this.next = null; this.prev = null; } } class DoublyLinkedList { constructor() { this.head = null; this.tail = null; this.length = 0; } // Insert at End insertAtEnd(value) { const newNode = new Node(value); if (!this.head) { this.head = newNode; this.tail = newNode; } else { this.tail.next = newNode; newNode.prev = this.tail; this.tail = newNode; } this.length++; return this; } // Delete operation delete(key) { let currentNode = this.head; // if head node itself holds the key to be deleted if (currentNode && currentNode.value === key) { this.head = currentNode.next; if (this.head) { this.head.prev = null; } else { this.tail = null; } this.length--; return this; } // if the key to be deleted is in between the list while (currentNode) { if (currentNode.value === key) { currentNode.prev.next = currentNode.next; if (currentNode.next) { currentNode.next.prev = currentNode.prev; } else { this.tail = currentNode.prev; } this.length--; return this; } currentNode = currentNode.next; } return false; } }
In the above code, we define a Node class that represents a node in a doubly linked list, with a "value" property and two references: "next" points to the next node in the list, and "prev" points to the previous node.
We also define a DoublyLinkedList class that represents the entire list, with a "head" property pointing to the first node in the list, a "tail" property pointing to the last node, and a "length" property keeping track of the number of nodes in the list.
Example:
Let's assume we have a doubly linked list with three nodes, and we want to remove the node with the value "banana" from the list using the
delete
method.const list = new DoublyLinkedList(); list.insertAtEnd("apple"); list.insertAtEnd("banana"); list.insertAtEnd("grape"); list.delete("banana"); console.log(list);
The output will be:
{ head: { value: 'apple', next: [Object], prev: null }, tail: { value: 'grape', next: null, prev: [Object] }, length: 2 }
In this example, we create a new instance of the DoublyLinkedList class and add three nodes to the list using the
insertAtEnd
method. Then, we call thedelete
method to remove the node with the value "banana". Theconsole.log
statement outputs the updated list object, which contains two nodes and their properties, without the removed node with the value "banana".Display Forward Operation
Display forward operation in a doubly linked list prints all the elements of the list in forward direction, starting from the head node.
The
displayForward
method iterates through the list in forward direction, starting from the head node, and prints each node's value using aconsole.log
statement.class Node { constructor(value) { this.value = value; this.next = null; this.prev = null; } } class DoublyLinkedList { constructor() { this.head = null; this.tail = null; this.length = 0; } // Insert at End insertAtEnd(value) { const newNode = new Node(value); if (!this.head) { this.head = newNode; this.tail = newNode; } else { this.tail.next = newNode; newNode.prev = this.tail; this.tail = newNode; } this.length++; return this; } // Display Forward Operation displayForward() { let current = this.head; let result = ''; while (current !== null) { result += `${current.value} -> `; current = current.next; } result += 'null'; console.log(result); } }
In the above code, we define a Node class that represents a node in a doubly linked list, with a "value" property and two references: "next" points to the next node in the list, and "prev" points to the previous node.
We also define a DoublyLinkedList class that represents the entire list, with a "head" property pointing to the first node in the list, a "tail" property pointing to the last node, and a "length" property keeping track of the number of nodes in the list.
Example:
Let's assume we have a doubly linked list with three nodes, and we want to print all the nodes' values in forward direction using the
displayForward
method.const list = new DoublyLinkedList(); list.insertAtEnd("apple"); list.insertAtEnd("banana"); list.insertAtEnd("grape"); list.displayForward();
The output will be:
apple -> banana -> grape -> null
In this example, we create a new instance of the DoublyLinkedList class and add three nodes to the list using the
insertAtEnd
method. Then, we call thedisplayForward
method to print all the nodes' values in forward direction. Theconsole.log
statement outputs the values "apple banana grape" separated by a space.Display Backward Operation
Display backward operation in a doubly linked list prints all the elements of the list in reverse direction, starting from the tail node.
The
displayBackward
method iterates through the list in backward direction, starting from the tail node, and prints each node's value using aconsole.log
statement.class Node { constructor(value) { this.value = value; this.next = null; this.prev = null; } } class DoublyLinkedList { constructor() { this.head = null; this.tail = null; this.length = 0; } // Insert at End insertAtEnd(value) { const newNode = new Node(value); if (!this.head) { this.head = newNode; this.tail = newNode; } else { this.tail.next = newNode; newNode.prev = this.tail; this.tail = newNode; } this.length++; return this; } // Display Backward Operation displayBackward() { let currentNode = this.tail; let output = ''; while (currentNode) { output += `${currentNode.value} -> `; currentNode = currentNode.prev; } output += 'null'; console.log(output); } }
In the above code, we define a Node class that represents a node in a doubly linked list, with a "value" property and two references: "next" points to the next node in the list, and "prev" points to the previous node.
We also define a DoublyLinkedList class that represents the entire list, with a "head" property pointing to the first node in the list, a "tail" property pointing to the last node, and a "length" property keeping track of the number of nodes in the list.
Example:
Let's assume we have a doubly linked list with three nodes, and we want to print all the nodes' values in backward direction using the
displayBackward
method.const list = new DoublyLinkedList(); list.insertAtEnd("apple"); list.insertAtEnd("banana"); list.insertAtEnd("grape"); list.displayBackward();
The output will be:
grape -> banana -> apple -> null
In this example, we create a new instance of the DoublyLinkedList class and add three nodes to the list using the
insertAtEnd
method. Then, we call thedisplayBackward
method to print all the nodes' values in backward direction. Theconsole.log
statement outputs the values "grape banana apple" separated by a space.
Summarizing Up
A doubly linked list is a data structure that consists of a sequence of nodes, where each node has a value and two pointers that reference the previous and next nodes in the sequence. This means that the list can be traversed in both forward and backward directions.
The main advantage of a doubly linked list over a singly linked list is that it allows for more efficient deletion of nodes, as it can be done in constant time without iterating through the list. Insertion and deletion of nodes can be performed at both ends of the list as well as at any position in the list, allowing for greater flexibility in manipulating the data.
Basic operations that can be performed on a doubly linked list include insertion, deletion, insertion at the end, deletion from the end, insertion after a specified node, deletion of a specified node, and display of the list in both forward and backward directions. These operations make doubly linked lists useful in a variety of applications where fast and efficient data manipulation is required.