Close Menu
ExamsmetaExamsmeta
  • Science
    • Physics
    • Chemistry
    • Mathematics
    • Biology
    • Geology
    • Computer Science
    • Environmental Science
    • Medical Science
  • Commerce
    • Accountancy
    • Business Studies
    • Business Administration
    • Bank Management
    • Economics
    • Finance
    • Management
    • Marketing
  • Humanities
    • History
    • Economics
    • Geography
    • Social Science
    • Sociology
    • Psychology
    • Philosophy
    • Political Science
  • Computer Science
    • Data Structures
    • Algorithms
    • Python
    • Machine Learning
    • Data Science
    • Data Analytics
    • System Design
    • Programming
    • Database Management
    • Web Development
    • DevOps
    • Linux Tutorials
  • Higher Studies
    • Aviation
    • Astronomy
    • Aeronautics
    • Agriculture
    • Architecture
    • Anthropology
    • Biotechnology
    • Energy Studies
    • Earth Science
    • Design Studies
    • Healthcare Studies
    • Engineering Studies
    • Statistical Studies
    • Computer Networking
    • Computer Applications
    • Wireless Communication
    • International Relations
    • Public Administration
    • Human Resources
    • Communication
  • Exams
    • Exams In India
    • Exams In America
    • Exams In Canada
    • Exams In The UK
    • Exams In Australia
    • Exams In New Zealand
    • Exam Results
  • More Menus
    • Articles
    • Biographies
    • General Knowledge
    • Education
    • Cybersecurity
    • Internet Working
    • Information Technology
    • Google Workspace
    • Microsoft Office
  • Website
    • About Examsmeta
    • Cookies Policy
    • Privacy Policy
    • Terms of Use
    • Contact Us
    • Email
Facebook Instagram X (Twitter) Pinterest YouTube Reddit
  • About
  • Cookies
  • Privacy
  • Terms
  • Contact
  • Email
Facebook Instagram X (Twitter) Pinterest YouTube LinkedIn
ExamsmetaExamsmeta
  • Science
    • Physics
    • Chemistry
    • Mathematics
    • Biology
    • Geology
    • Computer Science
    • Environmental Science
    • Medical Science
  • Commerce
    • Accountancy
    • Business Studies
    • Business Administration
    • Bank Management
    • Economics
    • Finance
    • Management
    • Marketing
  • Humanities
    • History
    • Economics
    • Geography
    • Social Science
    • Sociology
    • Psychology
    • Philosophy
    • Political Science
  • Computer Science
    • Data Structures
    • Algorithms
    • Python
    • Machine Learning
    • Data Science
    • Data Analytics
    • System Design
    • Programming
    • Database Management
    • Web Development
    • DevOps
    • Linux Tutorials
  • Higher Studies
    • Aviation
    • Astronomy
    • Aeronautics
    • Agriculture
    • Architecture
    • Anthropology
    • Biotechnology
    • Energy Studies
    • Earth Science
    • Design Studies
    • Healthcare Studies
    • Engineering Studies
    • Statistical Studies
    • Computer Networking
    • Computer Applications
    • Wireless Communication
    • International Relations
    • Public Administration
    • Human Resources
    • Communication
  • Exams
    • Exams In India
    • Exams In America
    • Exams In Canada
    • Exams In The UK
    • Exams In Australia
    • Exams In New Zealand
    • Exam Results
  • More Menus
    • Articles
    • Biographies
    • General Knowledge
    • Education
    • Cybersecurity
    • Internet Working
    • Information Technology
    • Google Workspace
    • Microsoft Office
  • Website
    • About Examsmeta
    • Cookies Policy
    • Privacy Policy
    • Terms of Use
    • Contact Us
    • Email
ExamsmetaExamsmeta
Data Structures

Operations of Doubly Linked List with Implementation: A Detailed Exploration

By Examsmeta
Share
Facebook Twitter Copy Link

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)
  • Operations on Doubly Linked List
  • Advantages of Doubly Linked List
  • Conclusion
  • Implementation in Multiple Programming Language
  • Time Complexity and Space Complexity
  • Read More Articles
  • Frequently Asked Questions (FAQs)

Structure of Doubly Linked List

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:

  1. Data: Stores the value of the node.
  2. Next Pointer: Points to the next node in the list.
  3. 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:

  1. Create a new node with the desired data.
  2. Set the next pointer of the new node to the current head.
  3. Update the prev pointer of the current head to the new node.
  4. Set the new node as the new head of the list.
  5. 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 to None.
  • 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:

  1. Traverse the list to find the last node.
  2. Set the next pointer of the last node to the new node.
  3. 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:

  1. Traverse to the node at the specified position.
  2. Update the next and prev 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

  1. Update the head to the next node.
  2. Set the prev pointer of the new head to None.

(b.) Deletion at the End

  1. Traverse to the second-last node.
  2. Set its next pointer to None.

(c.) Deletion at a Given Position

  1. Traverse to the node at the specified position.
  2. Update the next pointer of the previous node and the prev 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
  • C ++
  • C#
  • Java
  • JavaScript
  • Python

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

  1. Define the Node Structure:
    • A struct Node is defined with three members: key (data), prev (pointer to the previous node), and next (pointer to the next node).
  2. Global Variables:
    • head points to the first node.
    • tail points to the last node.
  3. Node Creation:
    • The createNode() function dynamically allocates memory for a new node and initializes its values.
  4. Adding Nodes:
    • The addNode() function appends nodes to the list. If the list is empty, it initializes the head and tail.
  5. Traversing the List:
    • The traverse() function iterates through the list, printing the keys of each node.
  6. Insertions:
    • insertAtBegin() adds a node at the front.
    • insertAtEnd() appends a node at the end by calling addNode().
    • insertAtPosition() adds a node at a specific position by adjusting pointers accordingly.
  7. Deletions:
    • deleteAtBegin() removes the first node and updates head.
    • deleteAtEnd() removes the last node and updates tail.
    • deleteAtPosition() removes a node at a specific position by traversing to it and updating pointers.
  8. 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

  1. Node structure: Represents a single node with data, prev, and next pointers.
  2. Add functions (addNode, insertAtBegin, etc.): Modularized to handle edge cases like an empty list or insertion at the boundaries.
  3. 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:

  1. Node Class: Represents each node with Data, Prev, and Next pointers.
  2. DoublyLinkedList Class: Encapsulates all operations on the DLL.
  3. Insert/Delete Functions: Handle all edge cases such as empty lists or invalid positions.
  4. 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

  1. Node Class:
    • Defines the structure of a node with data, prev (previous node), and next (next node) attributes.
    • Constructor initializes these values.
  2. DoublyLinkedList Class:
    • Maintains head (first node) and tail (last node) pointers.
    • Constructor initializes head and tail as null.
  3. 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 current tail.
    • traverse():
      • Iterates from head to tail, printing the data of each node.
    • insertAtBegin(int data):
      • Adds a node at the beginning by updating head and the next pointer of the new node.
    • insertAtEnd(int data):
      • Calls addNode(data) to append a node at the end.
    • 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.
    • deleteAtBegin():
      • Removes the first node and updates head. If the list becomes empty, tail is set to null.
    • deleteAtEnd():
      • Removes the last node and updates tail. If the list becomes empty, head is set to null.
    • 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.
  4. 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 position 3: 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.

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

  1. Node Class:
    • Represents an individual node with data, prev (previous pointer), and next (next pointer).
    • Constructor initializes the node with provided data and sets prev and next to null.
  2. DoublyLinkedList Class:
    • Manages the list with head (first node) and tail (last node).
    • Contains all operations like adding, traversing, inserting, and deleting nodes.
  3. addNode(data):
    • Adds a new node at the end.
    • Handles the case where the list is empty by initializing both head and tail.
  4. traverse():
    • Iterates from head to tail, collecting and printing the data of each node.
  5. insertAtBegin(data):
    • Creates a new node and links it as the new head.
    • Updates the prev pointer of the old head if the list is not empty.
  6. insertAtEnd(data):
    • Reuses addNode to add the node at the end of the list.
  7. 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.
  8. deleteAtBegin():
    • Removes the head node and updates the head pointer.
    • If the list becomes empty, sets both head and tail to null.
  9. deleteAtEnd():
    • Removes the tail node and updates the tail pointer.
    • Handles the case of an empty list.
  10. deleteAtPosition(position):
    • Traverses to the node at the given position and adjusts the prev and next pointers of its neighbors.
    • Handles deletion at the start or the end.

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

  1. Node Class:
    • Represents an individual node with data, prev, and next pointers.
    • Initializes data with the provided value, and sets both prev and next pointers to None.
  2. DoublyLinkedList Class:
    • Manages the doubly linked list with head (points to the first node) and tail (points to the last node).
  3. add_node(data):
    • Creates a new node and adds it at the end.
    • If the list is empty, initializes both head and tail with the new node.
  4. traverse():
    • Traverses the list from head to tail and collects the data of each node.
    • Prints the list in order.
  5. insert_at_beginning(data):
    • Creates a new node and inserts it at the beginning by updating the prev and next pointers of the current head and the new node.
  6. insert_at_end(data):
    • Reuses add_node(data) to insert a node at the end.
  7. insert_at_position(data, position):
    • Navigates to the specified position, creates a new node, and updates the prev and next pointers of the surrounding nodes.
  8. delete_at_beginning():
    • Removes the first node by updating the head to point to the next node. Adjusts prev pointers accordingly.
  9. delete_at_end():
    • Removes the last node by updating the tail to point to the previous node. Adjusts next pointers accordingly.
  10. delete_at_position(position):
    • Traverses to the specified position and removes the node by updating the prev and next pointers of the surrounding nodes.

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 list 1 -> 2 -> 4 -> 9.
  • Inserting at the End: Adding 10 at the end extends the list to 1 -> 2 -> 4 -> 9 -> 10.
  • Inserting at Position 3: Adding 5 at the 3rd position adjusts the list to 1 -> 2 -> 5 -> 4 -> 9 -> 10.
  • Deleting the First Node: Removing the first node (1) updates the list to 2 -> 5 -> 4 -> 9 -> 10.
  • Deleting the Last Node: Removing the last node (10) reduces the list to 2 -> 5 -> 4 -> 9.
  • Deleting Node at Position 2: Removing the node at position 2 (5) leaves the list as 2 -> 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.

OperationTime ComplexitySpace ComplexityExplanation
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

  1. C++:
    • Space complexity remains O(1) as memory is allocated only for the current node being processed using malloc.
  2. Java:
    • Space complexity remains O(1) because new is used for node creation, and no additional memory structures are utilized.
  3. Python:
    • Space complexity remains O(1), with dynamic memory management and no extra data structures.
  4. JavaScript:
    • Space complexity is O(1) since objects are created dynamically, and no additional space is required beyond the node itself.
  5. C:
    • Space complexity is O(1) as memory is allocated manually using malloc for each node.

Differences Between Languages

The time and space complexities are consistent across all the implementations due to the fundamental operations being identical. However:

  1. Memory Management:
    • In C and C++, manual memory allocation (malloc or new) is used, which might impact real-world performance slightly.
    • In Java, Python, and JavaScript, garbage collection manages memory automatically, leading to slight runtime overhead.
  2. 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 the explicit handling of pointers.

Read More Articles

  • Data Structure (DS) Array:
    1. Why the Analysis of Algorithms is Important?
    2. Worst, Average, and Best Case Analysis of Algorithms: A Comprehensive Guide
    3. Understanding Pointers in C Programming: A Comprehensive Guide
    4. Understanding Arrays in Data Structures: A Comprehensive Exploration
    5. Memory Allocation of an Array: An In-Depth Comprehensive Exploration
    6. Understanding Basic Operations in Arrays: A Comprehensive Guide
    7. Understanding 2D Arrays in Programming: A Comprehensive Guide
    8. Mapping a 2D Array to a 1D Array: A Comprehensive Exploration
  • Data Structure Linked List:
    1. Understanding Linked Lists in Data Structures: A Comprehensive Exploration
    2. Types of Linked List: Detailed Exploration, Representations, and Implementations
    3. Understanding Singly Linked Lists: A Detailed Exploration
    4. Understanding Doubly Linked List: A Comprehensive Guide
    5. Operations of Doubly Linked List with Implementation: A Detailed Exploration
    6. Insertion in Doubly Linked List with Implementation: A Detailed Exploration
    7. Inserting a Node at the beginning of a Doubly Linked List: A Detailed Exploration
    8. Inserting a Node After a Given Node in a Doubly Linked List: A Detailed Exploration
    9. Inserting a Node Before a Given Node in a Doubly Linked List: A Detailed Exploration
    10. Inserting a Node at a Specific Position in a Doubly Linked List: A Detailed Exploration
    11. Inserting a New Node at the End of a Doubly Linked List: A Detailed Exploration
    12. Deletion in a Doubly Linked List with Implementation: A Comprehensive Guide
    13. Deletion at the Beginning in a Doubly Linked List: A Detailed Exploration
    14. Deletion after a given node in Doubly Linked List: A Comprehensive Guide
    15. Deleting a Node Before a Given Node in a Doubly Linked List: A Detailed Exploration
    16. Deletion at a Specific Position in a Doubly Linked List: A Detailed Exploration
    17. Deletion at the End in Doubly Linked List: A Comprehensive Exploration
    18. Introduction to Circular Linked Lists: A Comprehensive Guide
    19. Understanding Circular Singly Linked Lists: A Comprehensive Guide
    20. Circular Doubly Linked List: A Comprehensive Guide
    21. Insertion in Circular Singly Linked List: A Comprehensive Guide
    22. Insertion in an Empty Circular Linked List: A Detailed Exploration
    23. Insertion at the Beginning in Circular Linked List: A Detailed Exploration
    24. Insertion at the End of a Circular Linked List: A Comprehensive Guide
    25. Insertion at a Specific Position in a Circular Linked List: A Detailed Exploration
    26. Deletion from a Circular Linked List: A Comprehensive Guide
    27. Deletion from the Beginning of a Circular Linked List: A Detailed Exploration
    28. Deletion at Specific Position in Circular Linked List: A Detailed Exploration
    29. Deletion at the End of a Circular Linked List: A Comprehensive Guide
    30. Searching in a Circular Linked List: A Comprehensive Exploration

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:

  1. Create a new node with the given data.
  2. Set the next pointer of the new node to the current head.
  3. Update the prev pointer of the current head to point to the new node.
  4. 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:

  1. Create a new node with the given data.
  2. If the list is empty, set the new node as the head.
  3. Otherwise, traverse to the last node of the list.
  4. Set the next pointer of the last node to the new node.
  5. 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:

  1. Check if the list is empty; if so, return.
  2. Update the head to the next node.
  3. Set the prev pointer of the new head to None.

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:

  1. Traverse to the last node of the list.
  2. Update the next pointer of the second-last node to None.
  3. 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:

  1. Start from the head node.
  2. Use a loop to print the data of each node while moving to the next 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:

  1. Traverse to the node at the given position or the one before it.
  2. Create a new node.
  3. 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:

  1. Traverse to the node at the given position.
  2. Update the next pointer of the previous node and the prev pointer of the next node.
  3. 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:

  1. Implementing undo/redo functionality in text editors.
  2. Navigating back and forth in web browsers.
  3. Managing playlists where both forward and backward navigation is required.
  4. 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.
Computer Science Higher Studies
Share. Facebook Twitter Copy Link
Examsmeta Logo
Examsmeta
  • Website
  • Facebook
  • X (Twitter)
  • Pinterest
  • Instagram
  • Tumblr
  • LinkedIn

Examsmeta serves as a comprehensive hub for educational resources across diverse disciplines. Designed to deliver high-quality, topic-wise notes and articles, it caters to students, educators, researchers, and lifelong learners. The goal is to make learning accessible, engaging, and effective for all. With a focus on providing detailed, accurate, and up-to-date content, Examsmeta fosters a passion for learning and supports both academic and professional growth. Whether it's exam preparation, research, or knowledge expansion, this platform offers guidance every step of the way.

Type above and press Enter to search. Press Esc to cancel.