Skip to content
Part IA Lent Term

Linked Lists

Overview

A linked list is a sequence of nodes, each containing data and one or more pointers to neighbouring nodes. Lists provide dynamic sizing (no pre-allocation needed) and constant-time insertion/deletion at known positions, at the cost of O(n)O(n) random access.

Singly Linked Lists

Each node has a value field and a next pointer. The list is accessed via a head pointer (and optionally a tail pointer for constant-time append).

Singly linked list diagram

Operations

OperationWithout tailWith tail
INSERT-HEADΘ(1)\Theta(1)Θ(1)\Theta(1)
INSERT-TAILΘ(n)\Theta(n)Θ(1)\Theta(1)
DELETE-HEADΘ(1)\Theta(1)Θ(1)\Theta(1)
DELETE(x) given node xxΘ(n)\Theta(n) (need predecessor)Θ(n)\Theta(n)
DELETE(x) given predecessorΘ(1)\Theta(1)Θ(1)\Theta(1)
SEARCH(k)Θ(n)\Theta(n)Θ(n)\Theta(n)

Insertion at head: create a new node, point its next to the current head, update head to the new node. This is Θ(1)\Theta(1).

Deletion of a node xx in a singly linked list requires finding the predecessor of xx (to update its next pointer), which takes Θ(n)\Theta(n) in the worst case via a linear scan from head. If the predecessor is already known (e.g. when iterating), deletion is Θ(1)\Theta(1).

Doubly Linked Lists

Each node has prev, value, and next pointers. The list maintains head and optionally tail.

Doubly linked list diagram

The key advantage: given a pointer to a node xx, deletion is Θ(1)\Theta(1) — we can reach its predecessor via x. ⁣prevx.\!prev without a scan.

DELETE(x):                    # x is a node in a doubly linked list
    if x.prev != NIL:
        x.prev.next = x.next
    else:
        L.head = x.next       # x was head
    if x.next != NIL:
        x.next.prev = x.prev

Insertion at head or after a known node is also Θ(1)\Theta(1):

INSERT-HEAD(L, k):
    x = new Node(prev=NIL, key=k, next=L.head)
    if L.head != NIL: L.head.prev = x
    L.head = x

Circular Variants

A circular linked list connects the last node back to the first (and optionally the first’s prev to the last in the doubly linked case). A sentinel node can be used as both head and tail, simplifying boundary checks.

Circular singly linked lists with tail pointer enable Θ(1)\Theta(1) insertion at both ends and Θ(1)\Theta(1) deletion at the front.

Comparison with Arrays

AspectArrayLinked List
Random access (ii-th element)Θ(1)\Theta(1)Θ(n)\Theta(n)
Insert/delete at headΘ(n)\Theta(n) (shift elements)Θ(1)\Theta(1)
Insert/delete at tailΘ(1)\Theta(1) amortised or Θ(n)\Theta(n)Θ(1)\Theta(1) with tail pointer
Insert/delete in middleΘ(n)\Theta(n) (shift)Θ(1)\Theta(1) if position known, Θ(n)\Theta(n) to find
SearchΘ(n)\Theta(n) linear, O(logn)O(\log n) if sortedΘ(n)\Theta(n)
Space overheadNone beyond array itself1–2 pointers per element
Cache localityExcellent (contiguous memory)Poor (scattered allocations)

Space Overhead

TypePointers per nodeNotes
Singly linked1 (next)Forward traversal only
Doubly linked2 (next, prev)Bidirectional; enables Θ(1)\Theta(1) deletion at known node
CircularSame as linear variantExtra pointer logic but no extra fields

Usage in Doug Lea’s Malloc

The classic malloc implementation uses a doubly linked list to track free memory chunks in the heap. Each chunk is preceded by a list node containing the chunk size and free/busy status. Free chunks are linked together; when a chunk is freed, it is coalesced with adjacent free chunks. This is a real-world application of doubly linked lists at massive scale.

Summary

PropertySingly LinkedDoubly Linked
Head insertΘ(1)\Theta(1)Θ(1)\Theta(1)
Tail insertΘ(n)\Theta(n) (or Θ(1)\Theta(1) with tail)Θ(1)\Theta(1) with tail
Delete given nodeΘ(n)\Theta(n) (need predecessor)Θ(1)\Theta(1)
SearchΘ(n)\Theta(n)Θ(n)\Theta(n)
Space per node1 pointer2 pointers
TraversalForward onlyBidirectional