A Doubly Linked List (DLL) is a powerful data structure that extends the functionality of a Singly Linked List by including an additional pointer called the previous pointer, alongside the next pointer and the data. This enhancement enables navigation in both forward and backward directions, making it particularly useful for scenarios requiring frequent traversal or updates. Below, we explore various operations on a Doubly Linked List, along with their implementation details and conceptual understanding.
Table of Contents
Introduction to Doubly Linked List (DLL)
In a Singly Linked List, each node contains a data field and a pointer to the next node in the sequence. However, its major limitation is the lack of backward traversal. To overcome this, the Doubly Linked List introduces an extra pointer that points to the previous node. This simple yet effective modification makes operations such as insertion, deletion, and traversal more versatile and efficient.
A typical node in a DLL is represented as follows:
class Node:
def __init__(self, data):
self.data = data
self.next = None
self.prev = None
Each node has three components:
- Data: Stores the value of the node.
- Next Pointer: Points to the next node in the list.
- Previous Pointer: Points to the previous node in the list.
Operations on Doubly Linked List
1. Adding a Node at the Front of DLL
Adding a new node at the beginning of a Doubly Linked List involves several pointer adjustments to ensure the list remains intact. This operation is particularly useful when implementing stack-like structures or managing priority queues.
Steps:
- Create a new node with the desired data.
- Set the
next
pointer of the new node to the current head. - Update the
prev
pointer of the current head to the new node. - Set the new node as the new head of the list.
- Increment the count of nodes (using a global variable).
Python Implementation:
class DoublyLinkedList:
def __init__(self):
self.head = None
self.count = 0
def add_at_front(self, data):
new_node = Node(data)
new_node.next = self.head
if self.head is not None:
self.head.prev = new_node
self.head = new_node
self.count += 1
Key Considerations:
- Ensure the
prev
pointer of the new head is set toNone
. - Update the global node count after every addition.
2. Traversal of a Doubly Linked List
Traversal is the process of visiting each node in the DLL to perform operations such as searching, printing, or updating data. Traversal can be done in two directions:
- Forward Traversal: Starts from the head and follows the
next
pointers. - Backward Traversal: Starts from the tail and follows the
prev
pointers.
Python Implementation:
def forward_traversal(self):
current = self.head
while current:
print(current.data, end=" ")
current = current.next
def backward_traversal(self):
current = self.head
while current.next: # Reach the last node
current = current.next
while current:
print(current.data, end=" ")
current = current.prev
Advantages of Traversal in DLL:
- Backward traversal is straightforward due to the
prev
pointer. - Dual-direction traversal provides flexibility in managing the list.
3. Insertion of a Node
Insertion is a fundamental operation in DLL and can be performed in three ways: at the beginning, at the end, or at a given position.
(a.) Insertion at the Beginning
This process is similar to adding a node at the front. The steps involve updating the head pointer and adjusting the prev
pointer of the previous head.
(b.) Insertion at the End
To insert a node at the end:
- Traverse the list to find the last node.
- Set the
next
pointer of the last node to the new node. - Update the
prev
pointer of the new node to the last node.
Python Implementation:
def add_at_end(self, data):
new_node = Node(data)
if not self.head:
self.head = new_node
return
current = self.head
while current.next:
current = current.next
current.next = new_node
new_node.prev = current
(c.) Insertion at a Given Position
Inserting at a specific position requires careful pointer management:
- Traverse to the node at the specified position.
- Update the
next
andprev
pointers of the nodes involved.
Python Implementation:
def insert_at_position(self, position, data):
if position == 0:
self.add_at_front(data)
return
new_node = Node(data)
current = self.head
for _ in range(position - 1):
current = current.next
new_node.next = current.next
if current.next:
current.next.prev = new_node
current.next = new_node
new_node.prev = current
4. Deletion of a Node
Deletion involves removing a node and ensuring the integrity of the DLL. Like insertion, deletion can occur in three ways: at the beginning, at the end, or at a specific position.
(a.) Deletion at the Beginning
- Update the head to the next node.
- Set the
prev
pointer of the new head toNone
.
(b.) Deletion at the End
- Traverse to the second-last node.
- Set its
next
pointer toNone
.
(c.) Deletion at a Given Position
- Traverse to the node at the specified position.
- Update the
next
pointer of the previous node and theprev
pointer of the next node.
Python Implementation:
def delete_at_position(self, position):
if position == 0:
if self.head:
self.head = self.head.next
if self.head:
self.head.prev = None
return
current = self.head
for _ in range(position):
current = current.next
if current.next:
current.next.prev = current.prev
if current.prev:
current.prev.next = current.next
Advantages of Doubly Linked List
- Bidirectional Traversal: Allows traversing the list in both directions.
- Efficient Insertion and Deletion: Modifications at any position are easier compared to a Singly Linked List.
- Flexibility: Suitable for use cases like undo functionality in editors and managing playlists.
Conclusion
The Doubly Linked List is a robust data structure offering versatility and efficiency in managing dynamic datasets. By leveraging its previous and next pointers, operations like insertion, deletion, and traversal are executed seamlessly, making it indispensable in scenarios requiring frequent updates or bidirectional navigation. Understanding and implementing these operations is foundational for mastering data structures in computer science.
Implementation in Multiple Programming Language
C Program Implementation
#include <stdio.h>
#include <stdlib.h>
// Define the structure for a doubly linked list node
struct Node {
int key;
struct Node* prev;
struct Node* next;
};
// Global pointers for head and tail
struct Node* head = NULL;
struct Node* tail = NULL;
// Function to create a new node
struct Node* createNode(int key) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->key = key;
newNode->prev = NULL;
newNode->next = NULL;
return newNode;
}
// Function to add a node at the end
void addNode(int key) {
struct Node* newNode = createNode(key);
if (head == NULL) {
head = tail = newNode;
} else {
tail->next = newNode;
newNode->prev = tail;
tail = newNode;
}
}
// Function to traverse and print the list
void traverse() {
struct Node* current = head;
while (current != NULL) {
printf("%d", current->key);
if (current->next != NULL)
printf(" -> ");
current = current->next;
}
printf("\n");
}
// Function to insert a node at the beginning
void insertAtBegin(int key) {
struct Node* newNode = createNode(key);
if (head == NULL) {
head = tail = newNode;
} else {
newNode->next = head;
head->prev = newNode;
head = newNode;
}
}
// Function to insert a node at the end
void insertAtEnd(int key) {
addNode(key); // Simply call the addNode function
}
// Function to insert a node at a given position
void insertAtPosition(int key, int pos) {
if (pos == 1) {
insertAtBegin(key);
return;
}
struct Node* current = head;
for (int i = 1; i < pos - 1 && current != NULL; i++) {
current = current->next;
}
if (current == NULL || current == tail) {
insertAtEnd(key);
} else {
struct Node* newNode = createNode(key);
newNode->next = current->next;
newNode->prev = current;
current->next->prev = newNode;
current->next = newNode;
}
}
// Function to delete a node at the beginning
void deleteAtBegin() {
if (head == NULL) return;
struct Node* temp = head;
head = head->next;
if (head != NULL) {
head->prev = NULL;
} else {
tail = NULL; // If the list is now empty
}
free(temp);
}
// Function to delete a node at the end
void deleteAtEnd() {
if (tail == NULL) return;
struct Node* temp = tail;
tail = tail->prev;
if (tail != NULL) {
tail->next = NULL;
} else {
head = NULL; // If the list is now empty
}
free(temp);
}
// Function to delete a node at a given position
void deleteAtPosition(int pos) {
if (pos == 1) {
deleteAtBegin();
return;
}
struct Node* current = head;
for (int i = 1; i < pos && current != NULL; i++) {
current = current->next;
}
if (current == NULL) return;
if (current == tail) {
deleteAtEnd();
} else {
current->prev->next = current->next;
current->next->prev = current->prev;
free(current);
}
}
// Main function to demonstrate operations
int main() {
// Initial List
addNode(2);
addNode(4);
addNode(9);
printf("Initial List:\n");
traverse();
// Insert at the beginning
insertAtBegin(1);
printf("After inserting 1 at the beginning:\n");
traverse();
// Insert at the end
insertAtEnd(10);
printf("After inserting 10 at the end:\n");
traverse();
// Insert at position 3
insertAtPosition(5, 3);
printf("After inserting 5 at position 3:\n");
traverse();
// Delete the first node
deleteAtBegin();
printf("After deleting the first node:\n");
traverse();
// Delete the last node
deleteAtEnd();
printf("After deleting the last node:\n");
traverse();
// Delete node at position 2
deleteAtPosition(2);
printf("After deleting node at position 2:\n");
traverse();
return 0;
}
Detailed Steps in the Code
- Define the Node Structure:
- A
struct Node
is defined with three members:key
(data),prev
(pointer to the previous node), andnext
(pointer to the next node).
- A
- Global Variables:
head
points to the first node.tail
points to the last node.
- Node Creation:
- The
createNode()
function dynamically allocates memory for a new node and initializes its values.
- The
- Adding Nodes:
- The
addNode()
function appends nodes to the list. If the list is empty, it initializes thehead
andtail
.
- The
- Traversing the List:
- The
traverse()
function iterates through the list, printing the keys of each node.
- The
- Insertions:
insertAtBegin()
adds a node at the front.insertAtEnd()
appends a node at the end by callingaddNode()
.insertAtPosition()
adds a node at a specific position by adjusting pointers accordingly.
- Deletions:
deleteAtBegin()
removes the first node and updateshead
.deleteAtEnd()
removes the last node and updatestail
.deleteAtPosition()
removes a node at a specific position by traversing to it and updating pointers.
- Main Function:
- Demonstrates the functionality by performing a series of insertions and deletions while printing the list after each operation.
C++ Implementation
#include <iostream>
using namespace std;
// Node structure for Doubly Linked List
struct Node {
int data;
Node* prev;
Node* next;
Node(int value) {
data = value;
prev = nullptr;
next = nullptr;
}
};
Node* head = nullptr; // Points to the start of the DLL
Node* tail = nullptr; // Points to the end of the DLL
// Function to add a node at the end of the DLL
void addNode(int value) {
Node* newNode = new Node(value);
if (!head) { // If DLL is empty
head = tail = newNode;
} else { // Add to the end
tail->next = newNode;
newNode->prev = tail;
tail = newNode;
}
}
// Function to traverse the DLL
void traverse() {
Node* temp = head;
while (temp) {
cout << temp->data << " ";
temp = temp->next;
}
cout << endl;
}
// Function to insert at the beginning
void insertAtBegin(int value) {
Node* newNode = new Node(value);
if (!head) { // If DLL is empty
head = tail = newNode;
} else {
newNode->next = head;
head->prev = newNode;
head = newNode;
}
}
// Function to insert at the end
void insertAtEnd(int value) {
addNode(value); // Reuse addNode function
}
// Function to insert at a specific position
void insertAtPosition(int value, int pos) {
if (pos == 1) {
insertAtBegin(value);
return;
}
Node* temp = head;
for (int i = 1; i < pos - 1; ++i) {
if (!temp) {
cout << "Position out of bounds!" << endl;
return;
}
temp = temp->next;
}
if (!temp) {
insertAtEnd(value);
return;
}
Node* newNode = new Node(value);
newNode->next = temp->next;
if (temp->next) {
temp->next->prev = newNode;
}
temp->next = newNode;
newNode->prev = temp;
if (!newNode->next) { // Update tail if inserted at the end
tail = newNode;
}
}
// Function to delete the first node
void deleteAtBegin() {
if (!head) {
cout << "List is empty!" << endl;
return;
}
Node* temp = head;
head = head->next;
if (head) {
head->prev = nullptr;
} else {
tail = nullptr; // List becomes empty
}
delete temp;
}
// Function to delete the last node
void deleteAtEnd() {
if (!tail) {
cout << "List is empty!" << endl;
return;
}
Node* temp = tail;
tail = tail->prev;
if (tail) {
tail->next = nullptr;
} else {
head = nullptr; // List becomes empty
}
delete temp;
}
// Function to delete at a specific position
void deleteAtPosition(int pos) {
if (pos == 1) {
deleteAtBegin();
return;
}
Node* temp = head;
for (int i = 1; i < pos; ++i) {
if (!temp) {
cout << "Position out of bounds!" << endl;
return;
}
temp = temp->next;
}
if (!temp) {
cout << "Position out of bounds!" << endl;
return;
}
if (temp->prev) {
temp->prev->next = temp->next;
}
if (temp->next) {
temp->next->prev = temp->prev;
}
if (temp == tail) {
tail = temp->prev;
}
delete temp;
}
// Driver Code
int main() {
addNode(2);
addNode(4);
addNode(9);
traverse();
insertAtBegin(1);
traverse();
insertAtEnd(10);
traverse();
insertAtPosition(5, 3);
traverse();
deleteAtBegin();
traverse();
deleteAtEnd();
traverse();
deleteAtPosition(2);
traverse();
return 0;
}
Explanation of Step
Node
structure: Represents a single node withdata
,prev
, andnext
pointers.- Add functions (
addNode
,insertAtBegin
, etc.): Modularized to handle edge cases like an empty list or insertion at the boundaries. - Delete functions: Properly manage pointers to ensure memory cleanup and handle cases like a single-node list.
Output
2 4 9
1 2 4 9
1 2 4 9 10
1 2 5 4 9 10
2 5 4 9 10
2 5 4 9
2 4 9
C# Implementation
using System;
public class Node {
public int Data;
public Node Prev;
public Node Next;
public Node(int data) {
Data = data;
Prev = null;
Next = null;
}
}
public class DoublyLinkedList {
private Node head;
private Node tail;
public DoublyLinkedList() {
head = null;
tail = null;
}
// Add node at the end
public void AddNode(int data) {
Node newNode = new Node(data);
if (head == null) {
head = tail = newNode;
} else {
tail.Next = newNode;
newNode.Prev = tail;
tail = newNode;
}
}
// Traverse the list
public void Traverse() {
Node temp = head;
while (temp != null) {
Console.Write(temp.Data + " ");
temp = temp.Next;
}
Console.WriteLine();
}
// Insert at the beginning
public void InsertAtBegin(int data) {
Node newNode = new Node(data);
if (head == null) {
head = tail = newNode;
} else {
newNode.Next = head;
head.Prev = newNode;
head = newNode;
}
}
// Insert at the end
public void InsertAtEnd(int data) {
AddNode(data);
}
// Insert at a specific position
public void InsertAtPosition(int data, int position) {
if (position == 1) {
InsertAtBegin(data);
return;
}
Node temp = head;
for (int i = 1; i < position - 1 && temp != null; i++) {
temp = temp.Next;
}
if (temp == null) {
Console.WriteLine("Position out of bounds!");
return;
}
Node newNode = new Node(data);
newNode.Next = temp.Next;
if (temp.Next != null) {
temp.Next.Prev = newNode;
}
temp.Next = newNode;
newNode.Prev = temp;
if (newNode.Next == null) {
tail = newNode;
}
}
// Delete at the beginning
public void DeleteAtBegin() {
if (head == null) {
Console.WriteLine("List is empty!");
return;
}
head = head.Next;
if (head != null) {
head.Prev = null;
} else {
tail = null;
}
}
// Delete at the end
public void DeleteAtEnd() {
if (tail == null) {
Console.WriteLine("List is empty!");
return;
}
tail = tail.Prev;
if (tail != null) {
tail.Next = null;
} else {
head = null;
}
}
// Delete at a specific position
public void DeleteAtPosition(int position) {
if (position == 1) {
DeleteAtBegin();
return;
}
Node temp = head;
for (int i = 1; i < position && temp != null; i++) {
temp = temp.Next;
}
if (temp == null) {
Console.WriteLine("Position out of bounds!");
return;
}
if (temp.Prev != null) {
temp.Prev.Next = temp.Next;
}
if (temp.Next != null) {
temp.Next.Prev = temp.Prev;
}
if (temp == tail) {
tail = temp.Prev;
}
}
}
// Driver Code
public class Program {
public static void Main() {
DoublyLinkedList dll = new DoublyLinkedList();
dll.AddNode(2);
dll.AddNode(4);
dll.AddNode(9);
dll.Traverse();
dll.InsertAtBegin(1);
dll.Traverse();
dll.InsertAtEnd(10);
dll.Traverse();
dll.InsertAtPosition(5, 3);
dll.Traverse();
dll.DeleteAtBegin();
dll.Traverse();
dll.DeleteAtEnd();
dll.Traverse();
dll.DeleteAtPosition(2);
dll.Traverse();
}
}
Explanation for C# Implementation:
- Node Class: Represents each node with
Data
,Prev
, andNext
pointers. - DoublyLinkedList Class: Encapsulates all operations on the DLL.
- Insert/Delete Functions: Handle all edge cases such as empty lists or invalid positions.
- Traverse Function: Prints the elements in the list.
Output
2 4 9
1 2 4 9
1 2 4 9 10
1 2 5 4 9 10
2 5 4 9 10
2 5 4 9
2 4 9
Java Implementation
class Node {
int data;
Node prev, next;
Node(int data) {
this.data = data;
this.prev = null;
this.next = null;
}
}
class DoublyLinkedList {
private Node head, tail;
public DoublyLinkedList() {
head = null;
tail = null;
}
public void addNode(int data) {
Node newNode = new Node(data);
if (head == null) {
head = tail = newNode;
} else {
tail.next = newNode;
newNode.prev = tail;
tail = newNode;
}
}
public void traverse() {
Node temp = head;
while (temp != null) {
System.out.print(temp.data + " ");
temp = temp.next;
}
System.out.println();
}
public void insertAtBegin(int data) {
Node newNode = new Node(data);
if (head == null) {
head = tail = newNode;
} else {
newNode.next = head;
head.prev = newNode;
head = newNode;
}
}
public void insertAtEnd(int data) {
addNode(data);
}
public void insertAtPosition(int data, int position) {
if (position == 1) {
insertAtBegin(data);
return;
}
Node temp = head;
for (int i = 1; i < position - 1 && temp != null; i++) {
temp = temp.next;
}
if (temp == null) {
System.out.println("Position out of bounds!");
return;
}
Node newNode = new Node(data);
newNode.next = temp.next;
if (temp.next != null) {
temp.next.prev = newNode;
}
temp.next = newNode;
newNode.prev = temp;
if (newNode.next == null) {
tail = newNode;
}
}
public void deleteAtBegin() {
if (head == null) {
System.out.println("List is empty!");
return;
}
head = head.next;
if (head != null) {
head.prev = null;
} else {
tail = null;
}
}
public void deleteAtEnd() {
if (tail == null) {
System.out.println("List is empty!");
return;
}
tail = tail.prev;
if (tail != null) {
tail.next = null;
} else {
head = null;
}
}
public void deleteAtPosition(int position) {
if (position == 1) {
deleteAtBegin();
return;
}
Node temp = head;
for (int i = 1; i < position && temp != null; i++) {
temp = temp.next;
}
if (temp == null) {
System.out.println("Position out of bounds!");
return;
}
if (temp.prev != null) {
temp.prev.next = temp.next;
}
if (temp.next != null) {
temp.next.prev = temp.prev;
}
if (temp == tail) {
tail = temp.prev;
}
}
}
public class Main {
public static void main(String[] args) {
DoublyLinkedList dll = new DoublyLinkedList();
dll.addNode(2);
dll.addNode(4);
dll.addNode(9);
dll.traverse();
dll.insertAtBegin(1);
dll.traverse();
dll.insertAtEnd(10);
dll.traverse();
dll.insertAtPosition(5, 3);
dll.traverse();
dll.deleteAtBegin();
dll.traverse();
dll.deleteAtEnd();
dll.traverse();
dll.deleteAtPosition(2);
dll.traverse();
}
}
Step by Step Explanation
- Node Class:
- Defines the structure of a node with
data
,prev
(previous node), andnext
(next node) attributes. - Constructor initializes these values.
- Defines the structure of a node with
- DoublyLinkedList Class:
- Maintains
head
(first node) andtail
(last node) pointers. - Constructor initializes
head
andtail
asnull
.
- Maintains
- Methods in DoublyLinkedList:
addNode(int data)
:- Creates a new node and appends it to the end of the list.
- Updates
tail
and connects the new node to the currenttail
.
traverse()
:- Iterates from
head
totail
, printing thedata
of each node.
- Iterates from
insertAtBegin(int data)
:- Adds a node at the beginning by updating
head
and thenext
pointer of the new node.
- Adds a node at the beginning by updating
insertAtEnd(int data)
:- Calls
addNode(data)
to append a node at the end.
- Calls
insertAtPosition(int data, int position)
:- Inserts a node at a specific position by traversing to
position - 1
. - Updates surrounding nodes’ pointers and adjusts
tail
if added at the end.
- Inserts a node at a specific position by traversing to
deleteAtBegin()
:- Removes the first node and updates
head
. If the list becomes empty,tail
is set tonull
.
- Removes the first node and updates
deleteAtEnd()
:- Removes the last node and updates
tail
. If the list becomes empty,head
is set tonull
.
- Removes the last node and updates
deleteAtPosition(int position)
:- Removes a node at a specific position by traversing to it.
- Updates surrounding nodes’ pointers and adjusts
tail
if the last node is deleted.
- Main Class:
- Creates a
DoublyLinkedList
instance (dll
). - Performs the following operations:
- Adds nodes
2
,4
,9
and prints:2 4 9
. - Inserts
1
at the beginning:1 2 4 9
. - Inserts
10
at the end:1 2 4 9 10
. - Inserts
5
at position3
:1 2 5 4 9 10
. - Deletes the first node:
2 5 4 9 10
. - Deletes the last node:
2 5 4 9
. - Deletes the node at position
2
:2 4 9
.
- Adds nodes
- Creates a
Output
2 4 9
1 2 4 9
1 2 4 9 10
1 2 5 4 9 10
2 5 4 9 10
2 5 4 9
2 4 9
JavaScript Implementation
class Node {
constructor(data) {
this.data = data; // Node value
this.prev = null; // Pointer to the previous node
this.next = null; // Pointer to the next node
}
}
class DoublyLinkedList {
constructor() {
this.head = null; // Head of the list
this.tail = null; // Tail of the list
}
// Add a node at the end of the list
addNode(data) {
const newNode = new Node(data);
if (this.head === null) {
this.head = this.tail = newNode;
} else {
this.tail.next = newNode;
newNode.prev = this.tail;
this.tail = newNode;
}
}
// Traverse and print the list
traverse() {
let temp = this.head;
const result = [];
while (temp !== null) {
result.push(temp.data);
temp = temp.next;
}
console.log(result.join(' '));
}
// Insert at the beginning
insertAtBegin(data) {
const newNode = new Node(data);
if (this.head === null) {
this.head = this.tail = newNode;
} else {
newNode.next = this.head;
this.head.prev = newNode;
this.head = newNode;
}
}
// Insert at the end
insertAtEnd(data) {
this.addNode(data);
}
// Insert at a specific position
insertAtPosition(data, position) {
if (position === 1) {
this.insertAtBegin(data);
return;
}
let temp = this.head;
for (let i = 1; i < position - 1 && temp !== null; i++) {
temp = temp.next;
}
if (temp === null) {
console.log("Position out of bounds!");
return;
}
const newNode = new Node(data);
newNode.next = temp.next;
if (temp.next !== null) {
temp.next.prev = newNode;
}
temp.next = newNode;
newNode.prev = temp;
if (newNode.next === null) {
this.tail = newNode;
}
}
// Delete the node at the beginning
deleteAtBegin() {
if (this.head === null) {
console.log("List is empty!");
return;
}
this.head = this.head.next;
if (this.head !== null) {
this.head.prev = null;
} else {
this.tail = null;
}
}
// Delete the node at the end
deleteAtEnd() {
if (this.tail === null) {
console.log("List is empty!");
return;
}
this.tail = this.tail.prev;
if (this.tail !== null) {
this.tail.next = null;
} else {
this.head = null;
}
}
// Delete the node at a specific position
deleteAtPosition(position) {
if (position === 1) {
this.deleteAtBegin();
return;
}
let temp = this.head;
for (let i = 1; i < position && temp !== null; i++) {
temp = temp.next;
}
if (temp === null) {
console.log("Position out of bounds!");
return;
}
if (temp.prev !== null) {
temp.prev.next = temp.next;
}
if (temp.next !== null) {
temp.next.prev = temp.prev;
}
if (temp === this.tail) {
this.tail = temp.prev;
}
}
}
// Driver Code
const dll = new DoublyLinkedList();
dll.addNode(2);
dll.addNode(4);
dll.addNode(9);
dll.traverse(); // Output: 2 4 9
dll.insertAtBegin(1);
dll.traverse(); // Output: 1 2 4 9
dll.insertAtEnd(10);
dll.traverse(); // Output: 1 2 4 9 10
dll.insertAtPosition(5, 3);
dll.traverse(); // Output: 1 2 5 4 9 10
dll.deleteAtBegin();
dll.traverse(); // Output: 2 5 4 9 10
dll.deleteAtEnd();
dll.traverse(); // Output: 2 5 4 9
dll.deleteAtPosition(2);
dll.traverse(); // Output: 2 4 9
Explanation of the JavaScript Code
- Node Class:
- Represents an individual node with
data
,prev
(previous pointer), andnext
(next pointer). - Constructor initializes the node with provided
data
and setsprev
andnext
tonull
.
- Represents an individual node with
- DoublyLinkedList Class:
- Manages the list with
head
(first node) andtail
(last node). - Contains all operations like adding, traversing, inserting, and deleting nodes.
- Manages the list with
- addNode(data):
- Adds a new node at the end.
- Handles the case where the list is empty by initializing both
head
andtail
.
- traverse():
- Iterates from
head
totail
, collecting and printing thedata
of each node.
- Iterates from
- insertAtBegin(data):
- Creates a new node and links it as the new
head
. - Updates the
prev
pointer of the oldhead
if the list is not empty.
- Creates a new node and links it as the new
- insertAtEnd(data):
- Reuses
addNode
to add the node at the end of the list.
- Reuses
- insertAtPosition(data, position):
- Traverses to the desired position, inserts the new node, and updates pointers for adjacent nodes.
- Handles edge cases for positions at the start or beyond the current list length.
- deleteAtBegin():
- Removes the
head
node and updates thehead
pointer. - If the list becomes empty, sets both
head
andtail
tonull
.
- Removes the
- deleteAtEnd():
- Removes the
tail
node and updates thetail
pointer. - Handles the case of an empty list.
- Removes the
- deleteAtPosition(position):
- Traverses to the node at the given position and adjusts the
prev
andnext
pointers of its neighbors. - Handles deletion at the start or the end.
- Traverses to the node at the given position and adjusts the
Output
2 4 9
1 2 4 9
1 2 4 9 10
1 2 5 4 9 10
2 5 4 9 10
2 5 4 9
2 4 9
Python Implementation
# Node class to represent a single node
class Node:
def __init__(self, data):
self.data = data # Node value
self.prev = None # Pointer to the previous node
self.next = None # Pointer to the next node
# DoublyLinkedList class to manage the list
class DoublyLinkedList:
def __init__(self):
self.head = None # Head of the list
self.tail = None # Tail of the list
# Add a node at the end of the list
def add_node(self, data):
new_node = Node(data)
if self.head is None: # If the list is empty
self.head = self.tail = new_node
else: # Add the new node at the end
self.tail.next = new_node
new_node.prev = self.tail
self.tail = new_node
# Traverse and print the list
def traverse(self):
current = self.head
result = []
while current: # Loop through the list
result.append(current.data)
current = current.next
print(" -> ".join(map(str, result)))
# Insert a node at the beginning
def insert_at_beginning(self, data):
new_node = Node(data)
if self.head is None: # If the list is empty
self.head = self.tail = new_node
else: # Insert the new node as the new head
new_node.next = self.head
self.head.prev = new_node
self.head = new_node
# Insert a node at the end
def insert_at_end(self, data):
self.add_node(data) # Reuse add_node function
# Insert a node at a specific position
def insert_at_position(self, data, position):
if position == 1: # Insert at the beginning
self.insert_at_beginning(data)
return
current = self.head
for _ in range(position - 2): # Traverse to one position before
if current is None:
print("Position out of bounds!")
return
current = current.next
if current is None or current.next is None: # If it's the end
self.add_node(data)
else: # Insert in the middle
new_node = Node(data)
new_node.next = current.next
new_node.prev = current
current.next.prev = new_node
current.next = new_node
# Delete the node at the beginning
def delete_at_beginning(self):
if self.head is None: # If the list is empty
print("List is empty!")
return
self.head = self.head.next # Move the head to the next node
if self.head is not None:
self.head.prev = None
else:
self.tail = None # If list becomes empty
# Delete the node at the end
def delete_at_end(self):
if self.tail is None: # If the list is empty
print("List is empty!")
return
self.tail = self.tail.prev # Move the tail to the previous node
if self.tail is not None:
self.tail.next = None
else:
self.head = None # If list becomes empty
# Delete a node at a specific position
def delete_at_position(self, position):
if position == 1: # Delete the first node
self.delete_at_beginning()
return
current = self.head
for _ in range(position - 1): # Traverse to the position
if current is None:
print("Position out of bounds!")
return
current = current.next
if current is None: # Invalid position
print("Position out of bounds!")
elif current.next is None: # Delete the last node
self.delete_at_end()
else: # Delete in the middle
current.prev.next = current.next
current.next.prev = current.prev
# Driver Code
dll = DoublyLinkedList()
# Add nodes to the list
dll.add_node(2)
dll.add_node(4)
dll.add_node(9)
print("Initial List:")
dll.traverse() # Output: 2 -> 4 -> 9
# Insert at the beginning
dll.insert_at_beginning(1)
print("\nAfter inserting 1 at the beginning:")
dll.traverse() # Output: 1 -> 2 -> 4 -> 9
# Insert at the end
dll.insert_at_end(10)
print("\nAfter inserting 10 at the end:")
dll.traverse() # Output: 1 -> 2 -> 4 -> 9 -> 10
# Insert at position 3
dll.insert_at_position(5, 3)
print("\nAfter inserting 5 at position 3:")
dll.traverse() # Output: 1 -> 2 -> 5 -> 4 -> 9 -> 10
# Delete at the beginning
dll.delete_at_beginning()
print("\nAfter deleting the first node:")
dll.traverse() # Output: 2 -> 5 -> 4 -> 9 -> 10
# Delete at the end
dll.delete_at_end()
print("\nAfter deleting the last node:")
dll.traverse() # Output: 2 -> 5 -> 4 -> 9
# Delete at position 2
dll.delete_at_position(2)
print("\nAfter deleting node at position 2:")
dll.traverse() # Output: 2 -> 4 -> 9
Step-by-Step Explanation
- Node Class:
- Represents an individual node with
data
,prev
, andnext
pointers. - Initializes
data
with the provided value, and sets bothprev
andnext
pointers toNone
.
- Represents an individual node with
- DoublyLinkedList Class:
- Manages the doubly linked list with
head
(points to the first node) andtail
(points to the last node).
- Manages the doubly linked list with
- add_node(data):
- Creates a new node and adds it at the end.
- If the list is empty, initializes both
head
andtail
with the new node.
- traverse():
- Traverses the list from
head
totail
and collects thedata
of each node. - Prints the list in order.
- Traverses the list from
- insert_at_beginning(data):
- Creates a new node and inserts it at the beginning by updating the
prev
andnext
pointers of the currenthead
and the new node.
- Creates a new node and inserts it at the beginning by updating the
- insert_at_end(data):
- Reuses
add_node(data)
to insert a node at the end.
- Reuses
- insert_at_position(data, position):
- Navigates to the specified position, creates a new node, and updates the
prev
andnext
pointers of the surrounding nodes.
- Navigates to the specified position, creates a new node, and updates the
- delete_at_beginning():
- Removes the first node by updating the
head
to point to the next node. Adjustsprev
pointers accordingly.
- Removes the first node by updating the
- delete_at_end():
- Removes the last node by updating the
tail
to point to the previous node. Adjustsnext
pointers accordingly.
- Removes the last node by updating the
- delete_at_position(position):
- Traverses to the specified position and removes the node by updating the
prev
andnext
pointers of the surrounding nodes.
- Traverses to the specified position and removes the node by updating the
Output
Initial List:
2 -> 4 -> 9
After inserting 1 at the beginning:
1 -> 2 -> 4 -> 9
After inserting 10 at the end:
1 -> 2 -> 4 -> 9 -> 10
After inserting 5 at position 3:
1 -> 2 -> 5 -> 4 -> 9 -> 10
After deleting the first node:
2 -> 5 -> 4 -> 9 -> 10
After deleting the last node:
2 -> 5 -> 4 -> 9
After deleting node at position 2:
2 -> 4 -> 9
Explanation of the Output
- Initial List: The doubly linked list starts with the nodes
2 -> 4 -> 9
, added sequentially. - Inserting at the Beginning: Adding
1
at the start makes the list1 -> 2 -> 4 -> 9
. - Inserting at the End: Adding
10
at the end extends the list to1 -> 2 -> 4 -> 9 -> 10
. - Inserting at Position 3: Adding
5
at the 3rd position adjusts the list to1 -> 2 -> 5 -> 4 -> 9 -> 10
. - Deleting the First Node: Removing the first node (
1
) updates the list to2 -> 5 -> 4 -> 9 -> 10
. - Deleting the Last Node: Removing the last node (
10
) reduces the list to2 -> 5 -> 4 -> 9
. - Deleting Node at Position 2: Removing the node at position 2 (
5
) leaves the list as2 -> 4 -> 9
.
Time Complexity and Space Complexity
Detailed table representing the Time Complexity and Space Complexity for each operation in the implementations across C++, Java, Python, JavaScript, and C Programming.
Operation | Time Complexity | Space Complexity | Explanation |
---|---|---|---|
Add Node at End (addNode ) | O(1) | O(1) | Adding at the end involves updating the next pointer of the current tail and assigning a new tail, which are constant-time operations. |
Traverse List (traverse ) | O(n) | O(1) | Traverses the entire list by iterating through n nodes. The additional space used is constant as no extra data structures are used. |
Insert at Beginning (insertAtBegin ) | O(1) | O(1) | Inserting at the beginning involves only pointer updates, making it a constant-time operation. |
Insert at End (insertAtEnd ) | O(1) | O(1) | Appending at the end utilizes addNode and takes constant time due to direct access to the tail . |
Insert at Position (insertAtPosition ) | O(n) | O(1) | Traverses the list to the specified position (up to n steps) and updates pointers, leading to linear time complexity. |
Delete at Beginning (deleteAtBegin ) | O(1) | O(1) | Removing the first node requires updating the head pointer and is a constant-time operation. |
Delete at End (deleteAtEnd ) | O(1) | O(1) | Direct access to the tail allows pointer updates in constant time. |
Delete at Position (deleteAtPosition ) | O(n) | O(1) | Traverses up to n nodes to reach the desired position, then adjusts pointers, leading to linear time complexity. |
Notes on Space Complexity Across Languages
- C++:
- Space complexity remains O(1) as memory is allocated only for the current node being processed using
malloc
.
- Space complexity remains O(1) as memory is allocated only for the current node being processed using
- Java:
- Space complexity remains O(1) because
new
is used for node creation, and no additional memory structures are utilized.
- Space complexity remains O(1) because
- Python:
- Space complexity remains O(1), with dynamic memory management and no extra data structures.
- JavaScript:
- Space complexity is O(1) since objects are created dynamically, and no additional space is required beyond the node itself.
- C:
- Space complexity is O(1) as memory is allocated manually using
malloc
for each node.
- Space complexity is O(1) as memory is allocated manually using
Differences Between Languages
The time and space complexities are consistent across all the implementations due to the fundamental operations being identical. However:
- Memory Management:
- In C and C++, manual memory allocation (
malloc
ornew
) is used, which might impact real-world performance slightly. - In Java, Python, and JavaScript, garbage collection manages memory automatically, leading to slight runtime overhead.
- In C and C++, manual memory allocation (
- Ease of Implementation:
- Python and JavaScript implementations are simpler due to the absence of manual memory management.
- C and C++ are more verbose due to explicit handling of pointers.
Frequently Asked Questions (FAQs)
What is a Doubly Linked List, and how does it differ from a Singly Linked List?
A Doubly Linked List (DLL) is a type of linked list in which each node contains three components:
- Data: Stores the value of the node.
- Next Pointer: Points to the next node in the list.
- Previous Pointer: Points to the previous node in the list.
This bidirectional linkage allows traversal in both forward and backward directions, unlike a Singly Linked List, which only has a single pointer (next) to traverse forward. This additional flexibility in DLLs makes them suitable for applications like undo operations in text editors or navigation systems.
How do you add a node at the beginning of a Doubly Linked List?
To insert a node at the beginning of a DLL:
- Create a new node with the given data.
- Set the
next
pointer of the new node to the current head. - Update the
prev
pointer of the current head to point to the new node. - Update the head to the new node.
Here’s a Python implementation:
def insert_at_front(self, data):
new_node = Node(data)
if self.head is None:
self.head = new_node
else:
new_node.next = self.head
self.head.prev = new_node
self.head = new_node
How do you add a node at the end of a Doubly Linked List?
To insert a node at the end of a DLL:
- Create a new node with the given data.
- If the list is empty, set the new node as the head.
- Otherwise, traverse to the last node of the list.
- Set the
next
pointer of the last node to the new node. - Set the
prev
pointer of the new node to the last node.
Here’s a Python implementation:
def insert_at_end(self, data):
new_node = Node(data)
if self.head is None:
self.head = new_node
else:
temp = self.head
while temp.next:
temp = temp.next
temp.next = new_node
new_node.prev = temp
How do you delete a node from the beginning of a Doubly Linked List?
To delete a node from the beginning:
- Check if the list is empty; if so, return.
- Update the head to the next node.
- Set the
prev
pointer of the new head toNone
.
Python implementation:
def delete_from_front(self):
if self.head is None:
return
self.head = self.head.next
if self.head:
self.head.prev = None
How do you delete a node from the end of a Doubly Linked List?
To delete a node from the end:
- Traverse to the last node of the list.
- Update the
next
pointer of the second-last node toNone
. - If the list has only one node, set the head to
None
.
Python implementation:
def delete_from_end(self):
if self.head is None:
return
temp = self.head
while temp.next:
temp = temp.next
if temp.prev:
temp.prev.next = None
else:
self.head = None
How can you traverse a Doubly Linked List?
To traverse a DLL:
- Start from the head node.
- Use a loop to print the
data
of each node while moving to thenext
node.
Python implementation:
def traverse(self):
temp = self.head
while temp:
print(temp.data, end=" ")
temp = temp.next
print()
Example output:
Linked List:
5 10 20 25
How do you insert a node at a specific position in a Doubly Linked List?
To insert a node at a specific position:
- Traverse to the node at the given position or the one before it.
- Create a new node.
- Update the pointers of the adjacent nodes to insert the new node.
Python Implementation:
def insert_at_position(self, data, pos):
if pos < 1:
print("Invalid position!")
return
new_node = Node(data)
temp = self.head
for _ in range(pos - 2):
if temp is None:
print("Position out of bounds!")
return
temp = temp.next
new_node.next = temp.next
if temp.next:
temp.next.prev = new_node
temp.next = new_node
new_node.prev = temp
How do you delete a node at a specific position in a Doubly Linked List?
To delete a node at a specific position:
- Traverse to the node at the given position.
- Update the
next
pointer of the previous node and theprev
pointer of the next node. - Remove the node.
Python Implementation:
def delete_at_position(self, pos):
if pos < 1 or self.head is None:
print("Invalid position!")
return
temp = self.head
for _ in range(pos - 1):
if temp is None:
print("Position out of bounds!")
return
temp = temp.next
if temp.prev:
temp.prev.next = temp.next
if temp.next:
temp.next.prev = temp.prev
if temp == self.head:
self.head = temp.next
What are some common use cases of Doubly Linked Lists?
Doubly Linked Lists are useful in:
- Implementing undo/redo functionality in text editors.
- Navigating back and forth in web browsers.
- Managing playlists where both forward and backward navigation is required.
- Memory management systems, as they allow quick insertion and deletion of blocks.
What are the advantages and disadvantages of using Doubly Linked Lists?
Advantages:
- Bi-directional traversal enables more flexibility.
- Efficient insertion and deletion at both ends compared to arrays.
- Allows insertion and deletion at a specific position in (O(1)) time if the node is already known.
Disadvantages:
- Requires more memory due to the extra pointer (
prev
). - More complex to implement and maintain than singly linked lists.
- Slightly slower than singly linked lists due to additional pointer updates.