Data structures · 15 / 19
Linked list visualization
A singly linked list stores each value in a node that links to the next node, so inserting at the head takes constant time but finding a value means walking the list.
Time: best case O(1), worst case O(n). Space: O(n). What Big-O notation means.
Last updated
About this lesson
- Concept it teaches
- Nodes and links; insert at the head, walk, search and delete
- Canonical source
- Knuth, TAOCP vol. 1, section 2.2.3 (linked allocation); Cormen et al., Introduction to Algorithms, section 10.2
- Builds on
- Nothing, start here
- Concept it teaches
- Nodes and links; insert at the head, walk, search and delete
- Canonical source
- Knuth, TAOCP vol. 1, section 2.2.3 (linked allocation); Cormen et al., Introduction to Algorithms, section 10.2
- Builds on
- Nothing, start here
I already know this: go to the check
See
+10 XP
Watch the real code run, one step at a time. Change the input and see what happens.
Predict first. Before you press Play, guess where the first number goes: it is inserted at the head, so what does head point at afterwards?
Run Linked list
- Size
- 0
- Operation
- none
- Step
- 1/72
- Current
- Comparing
- Done
- Ruled out
Linked list. It is empty. Waiting: 4, 8, 1, 6, 3, 9. Removed: empty.
Insert the numbers one at a time, alternately at the head and at the tail. Then search for the target, remove it, and search again.
Insert the numbers one at a time, alternately at the head and at the tail. Then search for the target, remove it, and search again.
Step 1/72comparisonlink change
With focus on the player: Space play · ← → step · Home End jump
Understand
+15 XP
The idea in plain words: why it works, what it costs, when to use it. One question at the end.
A singly linked list is a sequence of nodes. Each node holds a value and a link to the next node, and the last node's link is null. The list itself only remembers its first node, called head. Unlike an array, the nodes are not side by side: the only way from one to the next is to follow a link.
How it works
There are four operations. They change links; no value moves.
insertHead(x)makes a node whose link points at the old first node, then movesheadto it.insertTail(x)has no tail pointer to use, so it starts atheadand follows links until it reaches a node whose link isnull. That node's link is then pointed at the new node.search(x)starts atheadand follows links until it findsxor reachesnull.remove(x)walks the same way but keepsprev, the node just behindcur. Whencurholdsx,prev's link skips over it. Ifcuris the first node,headskips over it instead.
Time and space complexity
| Best case time | O(1) |
|---|---|
| Average case time | O(n) |
| Worst case time | O(n) |
| Extra space | O(n) |
- Insert at the head, O(1): it makes one node and changes two links, however long the list is.
- Search, tail insert and remove, O(n): they walk from the head, and a list gives no way to jump ahead. The best case is a target in the first node.
- Space, O(n): one node per value, plus one link each.
A worked example
Insert 4, 8, 1, 6 and 3, alternately at the head and at the tail, as the lesson's run does. Then search for 8, remove it, and search for it again. The table lists the nodes from head, and how many links the operation followed to get there.
| Operation | List, from head | Links followed |
|---|---|---|
| insertHead(4) | 4 | 0 |
| insertTail(8) | 4, 8 | 0 |
| insertHead(1) | 1, 4, 8 | 0 |
| insertTail(6) | 1, 4, 8, 6 | 2 |
| insertHead(3) | 3, 1, 4, 8, 6 | 0 |
| search(8) | 3, 1, 4, 8, 6 (found the 4th node) | 3 |
| remove(8) | 3, 1, 4, 6 | 3 |
| search(8) | 3, 1, 4, 6 (not found, returns null) | 4 |
The list ends as 3, 1, 4, 6, and only links changed along the way. The head inserts followed no links, while insertTail(6) had to follow two to find the last node: this is the cost of having no tail pointer, and it grows with the length. remove(8) stopped with prev at the node 4 and made 4's link skip over 8. The run records 6 writes: one link changed by each of the five inserts and one by the removal.
Pseudocode
L.head is the first node, x.value and x.next are a node's value and link, and NIL stands for null.
INSERT-HEAD(L, v)
x = a new node with value v
x.next = L.head
L.head = x
INSERT-TAIL(L, v)
x = a new node with value v, x.next = NIL
if L.head == NIL
L.head = x
return
cur = L.head
while cur.next != NIL
cur = cur.next
cur.next = x
SEARCH(L, v)
cur = L.head
while cur != NIL and cur.value != v
cur = cur.next
return cur // NIL if absent
REMOVE(L, v)
prev = NIL
cur = L.head
while cur != NIL and cur.value != v
prev = cur
cur = cur.next
if cur == NIL
return false
if prev == NIL
L.head = cur.next
else
prev.next = cur.next
return true
In INSERT-HEAD, the line x.next = L.head comes first, so the old list is never unreachable. The three functions that walk have the same loop, and each round of it follows one link.
When to use it, and when not to
- Use it when you add or remove at the front often and never need "the item at position k".
- It is a building block: the hash table keeps one list in each bucket.
- Avoid it when you need to jump to the k-th item, or search the same data many times. An array reaches any position in O(1), and a sorted array can use binary search.
Where it is used
- Adjacency lists for graphs. A graph stores, for each vertex, a list of its neighbors. Adding an edge is an insert at the head,
O(1), and a traversal such as breadth-first search walks one list per vertex. The total space is proportional to the number of vertices plus edges (CLRS, section 22.1). - Free lists in memory allocators. An allocator can keep its unused blocks linked together, using the free block's own memory for the link. Allocating takes the block at the head and freeing puts a block back at the head, each in
O(1), and the blocks do not have to be next to each other. - Caches and hash buckets. A least-recently-used (LRU) cache pairs a hash table with a doubly linked list ordered by use. The table finds a node at once, and the list moves it to the front by changing a few links, with no shifting. The buckets of the hash table lesson are linked lists too.
Common mistake and edge cases
Variations and related ideas
A doubly linked list adds a link to the previous node, so a node that you already hold can be removed in O(1) without searching for prev. A tail pointer makes insertTail O(1), and it is what lets a list serve as a queue. A circular list points the last node back at the first. Inserting and removing only at the head gives a stack. Compared with an array, a list never shifts elements, but it cannot jump to the k-th item, which is why a sorted array with binary search beats it for repeated searches.
One question to finish
How much work does inserting a node at the head of a linked list of n nodes take?
Predict
+20 XP
The run stops and asks what happens next. The real run says if you were right.
Practice
+30 XP
Order the lines, fill in the gap, trace it by hand. The real run corrects every answer.
Check yourself
Four questions from memory. They also join your daily review. Questions return on a spaced schedule: right answers come back later, misses come back tomorrow.