Linked Lists

Posted by Mallory Feuer on June 5, 2019

A Singly Linked List is like a scavenger hunt. The benefit of using Linked Lists is that there is constant insertion time O(1). Access, search and deletion time is all linear O(n). Here’s a table of how Linked Lists compare to other Data Structures in terms of time complexity.

Data Structure Access Search Insertion Deletion Special Cases
Array 1 n n n  
Stack n n 1 1  
Queue n n 1 1  
Linked List n n 1 n  
Hash Table - n n n In case of perfect hash function, costs would be O(1)
Binary Search Tree n n n n in case of balanced tree, costs would be O(log(n))

Build a Constructor Function for a Singular Node

First, we want to build a constructor function for a singular Node: new Node(value). A node has attributes for value and next.

class Node {

  constructor(value) {
    this.value = value;
    this.next = null;
  }

}

Build a Constructor Function for a Singly Linked List

Then we want to build a constructor function for a singly linked list: new SinglyLinkedList(). A singly linked list has attributes for head and length. We can add values that build new nodes, we can search the list for a node at a specific position, and we can delete a node from a specific position.

class SinglyLinkedList {

  constructor() {
    this._length = 0;
    this.head = null;
  }

  add(value) {
    const node = new Node(value);
    let currentNode = this.head;

    // is the list empty?
    if (!currentNode) {
      this.head = node;
      this._length++;

      return node;
    }

    // is the list not empty?
    while (currentNode.next) {
      currentNode = currentNode.next;
    }

    currentNode.next = node;
    this._length++;

    return node;
  }

  searchNodeAt(position) {
    let currentNode = this.head;

    // the position isn't valid? is the length of the list 0? is the position less than 1? or is the position greater than the length of the list? 
    this.isValidPosition(position)

    // the position is valid?
    for (let i = 1; i < position; i++) {
      currentNode = currentNode.next;
    }

    return currentNode;
  }

  removeNodeAt(position) {
    let currentNode = this.head;
    let beforeNodeToBeDeleted = null;
    let possiblyDeletedNode = null;
    let deletedNode = null;

    // the position isn't valid?
    this.isValidPosition(position)

    // are we removing the head node?
    if (position === 1) {
      this.head = currentNode.next
      deletedNode = currentNode;
      this._length--;

      return deletedNode;
    }

    // are we removing a node from the tail?
    for (let i = 1; i < position; i++) {
      // store the value of the currentNode to the beforeNodeToBeDeleted
      beforeNodeToBeDeleted = currentNode;
      // assume that the next node in the iterator could be deleted
      possiblyDeletedNode = currentNode.next; // { value: 11, next: { value: 12, next: null }}
      currentNode = currentNode.next;
    }

    beforeNodeToBeDeleted.next = possiblyDeletedNode.next;
    deletedNode = possiblyDeletedNode;
    this._length--;

    return deletedNode
  }

  isValidPosition(position) {
    if (this._length === 0 || position < 1 || position > this._length) {
      throw new Error("Unable to find a node at this position")
    }
  }
}

Then, in order use our constructor functions:

let list = new SinglyLinkedList() gives us:

SinglyLinkedList {_length: 0, head: null}

then if we added some nodes…

list.add(10)
list.add(11)

our Linked List will look like:

SinglyLinkedList{_length: 2, head: Node {
   value: 10, next: Node {
	    value 11, next: null
		}
	}
}

then if we wanted to delete a node…

console.log("Removed Node: ", list.removeNodeAt(2)) 

returns:

Removed Node: Node {value: 11, next: null}

and then our Linked List looks like:

SinglyLinkedList{_length: 1, head: Node {
   value: 10, next: null
	}
}

Thanks for reading!! Enjoy this educational Linked List gif (we can also reverse linked lists.. there are doubly linked lists. THE POSSIBILITIES!)