Part I · What Is a Computational Structure?
Chapter 3
Lists, Stacks, Queues, and Deques
mixed Standard structures; the admissibility languages of their operation sequences are classical formal-language facts made explicit here.
- Describe singly and doubly linked lists, and explain why reaching position costs pointer hops in a pointer machine.
- Express list navigation as a grammar of cursor moves (
next,prev). - Prove that the operation sequences a stack can execute from empty form a non-regular language, and count them.
- Explain the circular buffer as arithmetic addressing on the ring .
3.1 Linked lists
A singly linked list stores each element in a record holding the value and the address of the next record; a head cell holds the address of the first record. Records can live anywhere in memory. Nothing about record ’s address can be computed from : it is known only once record has been read.
Consider a machine that can obtain the address of a record only by reading it from the head cell or from a previously read record. Then reading the element at position of a singly linked list requires at least pointer dereferences after reading the head, and suffice.
By induction on : the address of record is stored only in record (or in the head when ), so record must be read first. Following the chain from the head reads records : dereferences after the head.
∎The hypothesis is the crux. On a word RAM nothing forbids computing an address, but for a list nothing makes the computed address correct: the representation offers no arithmetic relation between position and location. That is precisely the property the array has and the list lacks.
The list’s operations have the same denotations as the array’s (get, set, insert, delete on an abstract sequence) and different costs. Inserting after a node one already holds is three pointer writes, independent of ; finding that node is the walk. The SDK’s LinkedListStructure charges pointer for each hop, which is how the comparison at the end of Chapter 2 was counted.
3.1.1 Cursors and the grammar of navigation
A more grammatical view of a list replaces “position ” by a cursor and two moves, next and prev (the latter requires a doubly linked list, whose records also store the previous address). A navigation is then a word over , and its denotation is a partial function on positions:
A word moves the cursor by its net displacement , provided every prefix stays within . Executing on the list costs pointer hops. But the denotation of is determined by just three numbers: the net displacement and the minimum and maximum prefix displacements. A word of a million moves could be “compiled” into those three numbers once and then applied in constant time — if the representation let us jump by a computed displacement. A doubly linked list does not; an array does. This simple observation reappears in Chapter 13 as a theorem about which structures admit compiled operations.
3.2 Stacks
A stack supports push(x), pop, top and empty, with last-in-first-out semantics. Array-backed, every operation is amortized (pushes inherit the doubling analysis of Chapter 2). The interesting feature of the stack is not its cost but its language.
Ignore the pushed values and record only the sequence of move names. Starting from an empty stack, which words over are defined — never pop an empty stack?
Let be the set of words in which every prefix contains at least as many as . Then is exactly the set of words defined on the empty stack, is context-free, and is not regular. The number of words of length in is .
Definedness. A pop is defined iff the stack is non-empty, i.e. iff the number of pushes so far exceeds the number of pops so far; every pop in is defined iff the prefix condition holds.
Non-regularity. Suppose a DFA with states accepted . Among the words two, and with , lead to the same state. Then and are both accepted or both rejected; but the first is not in and the second is. Contradiction.
Context-freeness. is generated by (prefixes of Dyck words), or recognized by a one-counter automaton.
Counting. Words in are lattice paths of steps that never go below ; by the reflection principle their number is , the classical ballot-type count.
∎The proposition is old mathematics, but its lesson for this book is new emphasis: the admissibility language of a structure’s operations can be more complex than any finite automaton can check. A system that wants to validate stack-operation sequences before running them — as a Grammar-as-a-Service front end might (Chapter 19) — needs a counter, not a regular expression. The SDK sidesteps the issue by checking definedness in the semantic layer, call by call. We can let it count the defined words by brute force and compare with the formula:
from itertools import product
from math import comb
from cgtsdk.structures import StackStructure
for L in range(9):
defined = 0
for w in product(["push(0)", "pop"], repeat=L):
r = StackStructure().grammar().execute(" ".join(w), check_adequacy=False)
defined += bool(r.defined)
print(L, defined, comb(L, L // 2))
0 1 1
1 1 1
2 2 2
3 3 3
4 6 6
5 10 10
6 20 20
7 35 35
8 70 70
3.3 Queues and the ring
A FIFO queue supports enqueue, dequeue, front and len. The standard array representation is a circular buffer: a block of capacity , a head index , and a count . The element at queue position is stored at index , and
The positions of the queue are numbered in the ring , and both moves are arithmetic on that ring — addition modulo — so both cost (amortized, with doubling when full). The circular buffer is arithmetic addressing (CGT-DEF-009) with the integers replaced by a cyclic group: the same pattern, and the first hint that group structure is what makes some numerations work (Chapter 13 develops this for de Bruijn graphs and hypercubes). A deque adds push_front and pop_back — subtraction modulo — at the same cost.
The admissibility language of the queue, like that of the stack, is the set of words in which each dequeue is preceded by more enqueues than dequeues; ignoring values, it is the same language , because emptiness depends only on the count. What distinguishes the two structures is the semantics — which value comes out — not the syntax. This is a small but genuine instance of the book’s three layers: identical admissibility, different denotations, similar costs.
On push(1) push(2) pop the stack returns ; on enqueue(1) enqueue(2) dequeue the queue returns . Both words are admissible from empty, and both cost a constant number of steps per call.
3.4 When lists still win
Linked structures lose to arrays on indexed access, but they have advantages that the cost columns of this chapter do not show: stable addresses (a record never moves, so external pointers to it stay valid), splicing of whole sublists, and no reallocation spikes. These are representation properties, not grammar properties — and they are a reminder that “which representation is best” depends on the workload, a theme made quantitative in Chapter 12.
Compile the cursor word into (net displacement, minimum prefix displacement, maximum prefix displacement). For which starting positions in a list of length is defined?
Solution to Exercise 3.1
Prefix displacements run : net , minimum , maximum . Defined iff and , i.e. ; then .
Give a context-free grammar for different from the one in the proof of Proposition 3.2, and prove that it generates exactly .
Show that the set of words over that are defined on a stack of bounded capacity (where push on a full stack is undefined) is regular, and give its minimal DFA. How many states does it have?
Solution to Exercise 3.3
Track the height : states , push from to , pop from to , all states accepting. All states are distinguishable (from height , the word is rejected but accepted), so the minimal DFA has states.
A queue implemented with two stacks (an “inbox” and an “outbox”) gives amortized operations. Write its operations as words over the stacks’ moves, and prove the amortized bound with a potential function.
- In a linked list the location of element is not a function of ; reaching it takes dereferences. Cursor navigation is a grammar of
next/prevwords whose denotation is determined by three numbers, but the list cannot exploit that. - The operation words a stack can execute from empty form the non-regular, context-free language of Dyck prefixes, with words of length : admissibility can exceed finite-automaton power.
- The circular buffer is arithmetic addressing on the ring ; queues and deques cost amortized .
- Stacks and queues share an admissibility language and differ in semantics — the three layers in miniature.