Singly Linked List

ยท

3 min read

Introduction

  • A Linked List is a linear data structure
  • Each node contains data and a reference to the next node.

  • It allows traversal in one direction, from the head (first node) to the tail (last node)

Code

class Node {
  constructor(data) {
    this.data = data;
    this.next = null;
  }
}
class LinkedList {
  constructor() {
    this.head = null;
  }

  // Helper: Traverse to the node at a specific index
  _getNodeAtIndex(index) {
    let current = this.head;
    let i = 0;

    while (current && i < index) {
      current = current.next;
      i++;
    }

    return current;
  }

  // Helper: Check if the list is empty
  _isEmpty() {
    return this.head === null;
  }

  // Helper: Add node at the beginning
  _addToStart(newNode) {
    newNode.next = this.head;
    this.head = newNode;
  }

  // Add a node to the end of the list
  append(data) {
    const newNode = new Node(data);
    if (this._isEmpty()) {
      this._addToStart(newNode);
      return;
    }
    let current = this._getNodeAtIndex(this.length() - 1);
    current.next = newNode;
  }

  // Add a node to the beginning of the list
  prepend(data) {
    const newNode = new Node(data);
    this._addToStart(newNode);
  }

  // Add a node at a specific index
  addAtIndex(data, index) {
    if (index === 0) {
      this.prepend(data);
      return;
    }

    const previous = this._getNodeAtIndex(index - 1);

    if (!previous) {
      console.log("Index out of bounds");
      return;
    }

    const newNode = new Node(data);
    newNode.next = previous.next;
    previous.next = newNode;
  }

  // Remove a node at a specific index
  removeAtIndex(index) {
    if (this._isEmpty()) {
      return;
    }

    if (index === 0) {
      this.head = this.head.next;
      return;
    }

    const previous = this._getNodeAtIndex(index - 1);

    if (!previous || !previous.next) {
      console.log("Index out of bounds");
      return;
    }

    previous.next = previous.next.next;
  }

  // Remove the first node
  removeFromStart() {
    if (this._isEmpty()) {
      return;
    }
    this.head = this.head.next;
  }

  // Remove the last node
  removeFromEnd() {
    if (this._isEmpty()) {
      return;
    }

    if (!this.head.next) {
      this.head = null;
      return;
    }

    const previous = this._getNodeAtIndex(this.length() - 2);
    previous.next = null;
  }

  // Search for a node with specific data
  search(data) {
    let current = this.head;
    while (current) {
      if (current.data === data) {
        return current;
      }
      current = current.next;
    }
    return null; // If not found
  }

  display() {
    let current = this.head;
    const elements = [];
    while (current) {
      elements.push(current.data);
      current = current.next;
    }
    console.log(elements.join(" -> "));
  }

  length() {
    let count = 0;
    let current = this.head;
    while (current) {
      count++;
      current = current.next;
    }
    return count;
  }
}
const list = new LinkedList();
list.append(10);
list.append(20);
list.display(); // 10 -> 20

list.prepend(5);
list.display(); // 5 -> 10 -> 20

list.addAtIndex(15, 2);
list.display(); // 5 -> 10 -> 15 -> 20

list.removeAtIndex(1);
list.display(); // 5 -> 15 -> 20

list.removeFromStart();
list.display(); // 15 -> 20

list.removeFromEnd();
list.display(); // 15

Real-time Use

Linked lists are used in various real-time applications, including:

  • Implementing stacks and queues

  • Managing memory allocation in dynamic environments

  • Managing music playlists in media players

Complexity Analysis

  • Insertion/Deletion at the Beginning: O(1)

  • Insertion/Deletion at the End: O(n) โ€” Due to traversal to the end

  • Searching/Accessing an Element: O(n)

ย