Algo Linked Lists

These are TypeScript shapes for a queue, a stack, and a doubly linked list. Each one is built from linked nodes. A linked list is good for insertions and removals at the first or last node. Traversal is the tricky part.

The queue is first in, first out. Enqueue points the current tail at the new node and then moves the tail. Dequeue sets the head to the next node and clears the old link. Peek returns the head value.

The stack is last in, first out. Push and pop both work at the head. The doubly linked list keeps prev and next, with prepend, append, insertAt, and remove.

Linked Lists

  • Good for insertions / removals in the first or last node
  • Tricky to traverse

Queue

First in, first out (FiFo)

head            tail
1 ->  2 ->  3 ->  4

type Node<T> = {
  value: T
  next?: Node<T>
}

export default class Queue<T> {
  public length: number
  private head?: Node<T>
  private tail?: Node<T>

  constructor() {
    this.head = this.tail = undefined
    this.length = 0
  }

  // add node to the queue: point the current tail to the new node, set the tail to the new node
  enqueue(item: T): void {
    const node = { next: undefined, value: item }
    this.length++
    if (!this.tail) {
      this.tail = this.head = node
      return
    }
    this.tail.next = node
    this.tail = node
  }

  // remove node from the queue: set the current head to the next node, remove the previous heads connection
  dequeue(): T | undefined {
    if (!this.head) return undefined
    this.length--

    const head = this.head
    this.head = this.head.next

    // free
    head.next = undefined

    if (this.length === 0) this.tail = undefined

    return head.value
  }

  // get the heads value
  peek(): T | undefined {
    return this.head?.value
  }
}

Stack

Last in, First out (LiFo)

3    head
v
2
v
1    tail

type Node<T> = {
  value: T
  next?: Node<T>
}

export default class Stack<T> {
  public length: number
  private head?: Node<T>

  constructor() {
    this.head = undefined
    this.length = 0
    return
  }

  // Add to the stack: point new node to next head, set head to new node
  push(item: T): void {
    const node = { value: item, next: undefined } as Node<T>
    this.length++
    if (!this.head) {
      this.head = node
      return
    }
    node.next = this.head
    this.head = node
  }

  // Remove from stack: set head to next node, remove pointer of prev node
  pop(): T | undefined {
    if (!this.head) return undefined
    this.length--
    const prevHead = this.head
    this.head = prevHead.next
    prevHead.next = undefined
    return prevHead.value
  }

  peek(): T | undefined {
    return this.head?.value
  }
}

Double Linked Lists

type Node<T> = {
  value: T
  prev?: Node<T>
  next?: Node<T>
}

export default class DoublyLinkedList<T> {
  public length: number
  private head?: Node<T>
  private tail?: Node<T>

  constructor() {
    this.length = 0
    this.head = this.tail = undefined
  }

  prepend(item: T): void {
    const node = { value: item } as Node<T>
    ++this.length
    if (!this.head) {
      this.head = this.tail = node
      return
    }

    node.next = this.head
    this.head.prev = node
    this.head = node
  }

  insertAt(item: T, idx: number): void {
    if (idx > this.length) throw new Error("Oops")
    if (idx === this.length) this.append(item)
    if (idx === 0) this.prepend(item)
    else {
      const curr = this.getAt(idx)
      const node = { value: item } as Node<T>
      node.next = curr
      node.prev = curr?.prev
      if (node.prev) node.prev.next = node
      if (curr) curr.prev = node

      ++this.length
    }
  }

  append(item: T): void {
    ++this.length
    const node = { value: item } as Node<T>
    if (!this.tail) {
      this.head = this.tail = node
      return
    }

    node.prev = this.tail
    this.tail.next = node
    this.tail = node
  }

  remove(item: T): T | undefined {
    let curr = this.head
    for (let index = 0; curr && curr.value !== item && index < this.length; index++) curr = curr.next
    return this.removeNode(curr)
  }

  get(idx: number): T | undefined {
    return this.getAt(idx)?.value
  }

  getAt(idx: number): Node<T> | undefined {
    let current = this.head
    for (let i = 0; i < idx && current; i++) current = current.next
    return current
  }

  removeAt(idx: number): T | undefined {
    const curr = this.getAt(idx)
    return this.removeNode(curr)
  }

  removeNode(node?: Node<T>): T | undefined {
    if (!node) return

    --this.length
    if (this.length === 0) {
      const out = this.head?.value
      this.head = this.tail = undefined
      return out
    }
    if (node.prev) node.prev.next = node.next
    if (node.next) node.next.prev = node.prev
    if (node === this.head) this.head = node.next
    if (node === this.tail) this.tail = node.prev
    node.prev = node.next = undefined

    return node.value
  }
}

See also: Algo Arrays Searching Sorting · Data Structures Overview · Algo Trees