Singly Linked Lists
Node pointer architecture, in-place 3-pointer reversal invariants, fast/slow Floyd cycle detection mathematical proofs.
Reinforce your mental model: run, pause, and inspect live pointers with our interactive visualizer:
Topics Covered:
24. Singly Linked List Architecture & Heap Memory Layout • Core Node Operations (Insert, Delete, Search, Access) • In-Place 3-Pointer Reversal • Floyd's Cycle-Finding Algorithm & Cycle Start Derivation • Fast & Slow Pointer Patterns
Unlike contiguous arrays that rely on physical hardware adjacency for element ordering, linked lists build logical sequences through explicit directional pointers scattered across heap memory. This architectural decoupling enables true dynamic insertions and deletions at the list boundary without memory reallocations or element shifts, but trades away constant-time indexing and hardware cache locality. This chapter details non-contiguous node topologies, memory alignment overheads, boundary-pointer manipulation invariants, the three-pointer in-place reversal state machine, and the mathematical proof underpinning Floyd's Tortoise and Hare cycle detection algorithm.
Learning Objectives
#- Contrast the physical heap allocation and CPU cache-line miss rates of linked nodes against contiguous memory arrays.
- Calculate pointer memory overhead and 64-bit alignment padding for node-based data structures.
- Implement head, tail, and arbitrary position insertions and deletions with strict boundary-condition checks.
- Formulate the loop invariant for canonical three-pointer in-place list reversal and trace state transitions step-by-step.
- Formulate the mathematical proof of Floyd's Cycle-Finding Algorithm and derive why resetting one pointer to
headguarantees collision at the cycle entrance. - Apply fast-and-slow two-pointer patterns to find list midpoints and the -th element from the end in a single pass.
Topic 24: Singly Linked List Architecture & Operations
#1. Conceptual & Physical Memory Foundations
A Singly Linked List is a linear data structure composed of self-contained Nodes, where each node encapsulates a data payload and a forward reference (pointer) to the next node in the sequence. The list begins with an external pointer called HEAD and terminates when a node's pointer references NULL.
Node Architecture in 64-Bit Memory
┌───────────────────────────────────────────────────────────────┐
│ 64-BIT NODE LAYOUT │
├───────────────────────────────┬───────────────────────────────┤
│ Data Payload (4 bytes) │ Next Pointer (8 bytes) │
│ + 4 bytes padding │ Points to Heap Address │
└───────────────────────────────┴───────────────────────────────┘| Field Name | Type | Size (64-Bit OS) | Alignment / Offset | Semantic Function |
|---|---|---|---|---|
data | Value / Primitive | Offset | Holds client payload (e.g., 32-bit integer) | |
padding | System Alignment | Offset | Structure padding to satisfy 8-byte pointer alignment | |
next | Memory Address | Offset | Stores virtual heap address of subsequent node |
Physical Heap Allocation Reality
While an array requires an unbroken contiguous block of memory, linked list nodes are allocated dynamically at arbitrary, dispersed addresses across the heap.
| Logical Sequence | Virtual Heap Address | Node Payload | next Pointer Target | Target Node Description |
|---|---|---|---|---|
Node 1 (HEAD) | 0x10A0 | 10 | 0x20F4 | Points to Node 2 |
| (Intervening Heap) | 0x10B0..0x20F0 | — | — | Used by unrelated system allocations |
| Node 2 | 0x20F4 | 20 | 0x15C8 | Points to Node 3 |
Node 3 (TAIL) | 0x15C8 | 30 | NULL (0x0) | Terminal Sentinel (End of List) |
⚠️ Memory Overhead & Cache Penalty:
Storing a 4-byte integer in a linked list node consumes in physical RAM due to pointer storage () and 64-bit alignment padding (), representing a memory penalty over a flat array. Furthermore, because adjacent logical nodes reside at distant heap addresses, traversing links causes frequent CPU L1/L2 cache misses.
2. Operations & Asymptotic Complexities
#| Operation | Best Case | Average Case | Worst Case | Auxiliary Space | Operational Invariants & Mechanism |
|---|---|---|---|---|---|
| InsertAtHead() | newNode.next = head; head = newNode; | ||||
InsertAtTail() (with tail ptr) | tail.next = newNode; tail = newNode; | ||||
InsertAtTail() (no tail ptr) | Must traverse nodes to find terminal node | ||||
| InsertAtPosition() | if ; otherwise requires steps | ||||
| DeleteHead() | Advance head to head.next; free old node | ||||
DeleteTail() (even with tail) | Must scan from head to locate -th predecessor | ||||
| DeleteByValue() | if target is head; linear scan | ||||
| Search() | Sequential scan from head | ||||
| AccessByIndex() | Pointer arithmetic impossible; requires pointer hops | ||||
| ReverseInPlace() | 3-pointer sliding window reassignment |
Interactive Simulation:
Step through pointer links, insertions, and traversals live in the Interactive Linked List Visualizer.
3. Step-by-Step Pointer Transitions
#A. Head Insertion ()
Inserting a new node with value 10 before existing head node 20:
| Step | Action Taken | Target Link Modified | Pointer State Result |
|---|---|---|---|
| 1 | Allocate newNode | newNode.data = 10 | newNode -> [10 | ?] |
| 2 | Link newNode to current head | newNode.next = HEAD | newNode -> [10] -> [20] -> [30] -> NULL |
| 3 | Reassign HEAD pointer | HEAD = newNode | HEAD -> [10] -> [20] -> [30] -> NULL |
B. Insertion at Arbitrary Position ()
Inserting node 25 between node 20 (index 1) and node 30 (index 2):
| Step | Action Taken | Target Link Modified | Critical Pointer Safety Rule |
|---|---|---|---|
| 1 | Traverse to predecessor curr | curr = Node(20) | Stop traversal at index |
| 2 | Connect new node forward | newNode.next = curr.next | Connect forward first! If curr.next is overwritten first, tail is lost forever |
| 3 | Connect predecessor forward | curr.next = newNode | List integrity restored: ... -> [20] -> [25] -> [30] -> ... |
C. The Tail Deletion Bottleneck in Singly Linked Lists
Even if an implementation maintains an explicit tail pointer to the final node, DeleteTail remains strictly .
HEAD -> [10] -> [20] -> [30] -> NULL (tail points to [30])
To delete [30], the tail pointer must become [20], and [20].next must become NULL.
Because singly linked nodes lack backward (prev) pointers, there is no direct way
to inspect the predecessor of tail. The algorithm must traverse n - 1 nodes from HEAD
simply to discover that [20] precedes [30].4. Comprehensive Production Specification
#CLASS Node:
field data: ValueType
field next: Node Pointer <- NULL
CONSTRUCTOR(val: ValueType):
this.data <- val
this.next <- NULL
CLASS SinglyLinkedList:
field head: Node Pointer <- NULL
field tail: Node Pointer <- NULL
field size: Integer <- 0
FUNCTION InsertAtHead(val: ValueType) -> Void:
newNode <- new Node(val)
newNode.next <- this.head
this.head <- newNode
if this.size == 0:
this.tail <- newNode
this.size <- this.size + 1
FUNCTION InsertAtTail(val: ValueType) -> Void:
newNode <- new Node(val)
if this.size == 0:
this.head <- newNode
this.tail <- newNode
else:
this.tail.next <- newNode
this.tail <- newNode
this.size <- this.size + 1
FUNCTION DeleteHead() -> ValueType:
if this.head == NULL:
raise UnderflowException("List is empty")
temp <- this.head
val <- temp.data
this.head <- this.head.next
if this.head == NULL:
this.tail <- NULL
this.size <- this.size - 1
free(temp)
return val
FUNCTION DeleteTail() -> ValueType:
if this.head == NULL:
raise UnderflowException("List is empty")
if this.head == this.tail:
val <- this.head.data
free(this.head)
this.head <- NULL
this.tail <- NULL
this.size <- 0
return val
// Traverse to second-to-last node
curr <- this.head
while curr.next != this.tail:
curr <- curr.next
val <- this.tail.data
free(this.tail)
this.tail <- curr
this.tail.next <- NULL
this.size <- this.size - 1
return val5. In-Place 3-Pointer List Reversal
#Reversing a singly linked list in-place without allocating auxiliary nodes requires a sliding window of three pointers: prev, curr, and nextNode.
Invariant & Execution Loop
- Loop Invariant: At the start of each iteration, the sublist preceding
curris fully reversed withprevreferencing its new head.currreferences the head of the unreversed remaining sublist.
FUNCTION ReverseList(head: Node Pointer) -> Node Pointer:
prev <- NULL
curr <- head
while curr != NULL:
nextNode <- curr.next // 1. Preserve forward link
curr.next <- prev // 2. Invert link to point backwards
prev <- curr // 3. Advance prev window
curr <- nextNode // 4. Advance curr window
return prev // prev points to new reversed headStep-by-Step State Trace: Reversing
| Step / Iteration | prev | curr | nextNode (curr.next) | Link Mutation (curr.next <- prev) | Next prev | Next curr |
|---|---|---|---|---|---|---|
| Initialization | NULL | Node(10) | — | — | — | — |
| Iteration 1 | NULL | Node(10) | Node(20) | Node(10).next = NULL | Node(10) | Node(20) |
| Iteration 2 | Node(10) | Node(20) | Node(30) | Node(20).next = Node(10) | Node(20) | Node(30) |
| Iteration 3 | Node(20) | Node(30) | NULL | Node(30).next = Node(20) | Node(30) | NULL |
| Termination | Node(30) | NULL | — | Loop terminates (curr == NULL). Return prev = Node(30). | — | — |
Final Reconstructed State: .
Complexity: Strictly time, auxiliary space.
6. Floyd's Cycle-Finding Algorithm (Tortoise & Hare)
#Floyd's algorithm determines whether a linked list contains a cycle and identifies the exact entry node of that cycle using two pointers moving at different speeds, requiring only auxiliary memory.
Mathematical Proof of Detection & Entry Derivation
List Topology:
HEAD -> [Node 1] -> [Node 2] -> [Cycle Entry: Node 3] -> [Node 4] -> [Node 5]
^ |
| v
[Node 8] <-------- [Node 7] <------ [Node 6]Let:
- = Distance from
HEADto the cycle entry node ( in the diagram above: links and ). - = Number of nodes in the cycle (: nodes ).
- = Distance from the cycle entry node to the meeting point where
slowandfastcollide.
Step 1: Detection Proof
slowadvances node per step;fastadvances nodes per step.- The relative velocity is node per step.
- Once both pointers enter the cycle,
fastreduces the gap by node on every iteration. Since the maximum possible gap is ,fastmust collide withslowwithin at most iterations inside the cycle. An infinite loop is mathematically impossible.
Step 2: Cycle Entry Equation Derivation
Let be the total steps taken by slow when the collision occurs. Because fast travels at double speed, its total distance is :
Subtracting the first equation from the second:
The total steps is an exact integer multiple of the cycle length .
Substituting into the slow distance formula:
The Critical Identity:
The term is the exact distance remaining from the collision point to the cycle entry node.
Therefore, the distance from HEAD to the cycle entry () equals the distance from the collision point to the cycle entry (), modulo full loops.
Algorithmic Resolution:
- When
slowandfastmeet at the collision point, leavefastat the collision point. - Reset
slowtoHEAD. - Advance both
slowandfastat a uniform speed of 1 step per iteration. - Both pointers will collide at the exact cycle entry node!
FUNCTION DetectAndFindCycleEntry(head: Node Pointer) -> Node Pointer:
slow <- head
fast <- head
hasCycle <- False
// Phase 1: Detect cycle
while fast != NULL and fast.next != NULL:
slow <- slow.next
fast <- fast.next.next
if slow == fast:
hasCycle <- True
break
if not hasCycle:
return NULL
// Phase 2: Find cycle entry node
slow <- head
while slow != fast:
slow <- slow.next
fast <- fast.next
return slow7. Fast & Slow Pointer Paradigms
#| Application | Pointer Initialization | Movement Rule | Termination Condition & Result |
|---|---|---|---|
| Find Midpoint of List | slow = head, fast = head | slow += 1, fast += 2 | When fast.next == NULL or fast == NULL, slow is at (essential for Merge Sort) |
| Find -th Node from End | fast advances steps ahead of slow | slow += 1, fast += 1 | When fast == NULL, slow points to exactly the -th node from the tail |
| Palindrome Verification | Find midpoint via fast/slow | Reverse second half in-place | Compare first half and reversed second half; restore list before returning |
8. Key Takeaways
#- Trade-offs vs. Arrays: Linked lists eliminate the need for large contiguous memory blocks and achieve true head insertions, but forfeit random indexing and suffer from poor CPU cache locality.
- Pointer Overhead: 64-bit systems impose an 8-byte pointer cost and 4-byte padding per node, causing up to a memory footprint increase for 32-bit payloads.
- Tail Deletion Vulnerability: Even with an explicit
tailreference,DeleteTailon a singly linked list requires time to scan for the penultimate node. - Three-Pointer Reversal: Inverting link references requires holding
nextNodebefore mutatingcurr.next, advancingprevandcurrin lockstep. - Floyd's Mathematical Harmony: The distance from
HEADto the cycle entrance equals the distance from the collision point to the entrance, enabling time, space cycle entrance discovery.
Academic Attribution & References
#- Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2022). Introduction to Algorithms (4th ed.), Chapter 10: Elementary Data Structures. MIT Press.
- Floyd, R. W. (1967). Non-deterministic Algorithms. Journal of the ACM, 14(4), 636-644.
- Sedgewick, R., & Wayne, K. (2011). Algorithms (4th ed.), Section 1.3: Bags, Queues, and Stacks. Addison-Wesley.
- Knuth, D. E. (1997). The Art of Computer Programming, Volume 1: Fundamental Algorithms (3rd ed.), Section 2.2: Linear Lists. Addison-Wesley.