Use a stack, queue and linked list to store data

10.4 Introduction to Abstract Data Types (ADT)

Abstract Data Type (ADT) – a logical description of a data structure that defines:

  • the set of values it can hold,
  • the operations that may be performed on those values,
  • the mathematical properties (invariants, pre‑ and post‑conditions) each operation must satisfy.

The ADT hides the underlying implementation (array, linked nodes, etc.). In the Cambridge 9618 Computer Science syllabus this distinction is expressed as the interface (what the ADT does) versus the implementation (how it does it).

1. Syllabus mapping – where ADTs sit in the Cambridge programme

AS‑Level block (1‑12)Relevant ADT content
10 Data types & structuresIntroduction to ADTs, Stack, Queue, Linked List (singly, doubly, circular)
9 AlgorithmsUse of stacks/queues in expression evaluation, BFS, back‑tracking
8 DatabasesLinked list as basis for dynamic storage (e.g., adjacency lists)
11 Programming constructsImplementing ADTs in pseudocode / a programming language

A‑Level extension block (13‑20)Advanced ADT topics
13 User‑defined data typesDesigning custom ADTs (e.g., priority queue, deque) using linked structures
14 File organisationLinked‑list based file allocation tables
15 Floating‑point representationStacks for expression evaluation involving real numbers
16 Network protocols & switchingQueue models for packet buffering
17 Parallel processing & virtual machinesConcurrent stacks/queues, lock‑free linked lists
18 Boolean algebra & Karnaugh mapsNot directly ADT‑related – listed for completeness
19 Operating system functionsProcess control blocks stored in linked lists; ready queues
20 Advanced algorithms & AIGraph algorithms using adjacency‑list (linked list) representation; search trees

Exam‑focus (AO) reminder

  • AO1 – Knowledge & understanding: define an ADT, list its core operations, state invariants.
  • AO2 – Application: choose the appropriate ADT for a given problem, write pseudocode for its operations.
  • AO3 – Analysis: compare time‑complexity, memory use and suitability of different ADT implementations.


2. Stack – LIFO (Last‑In‑First‑Out)

2.1 Interface (operations)

  • push(x) – insert element x on the top.
  • pop() – remove and return the top element.
  • peek() – return the top element without removing it.
  • isEmpty() – true iff the stack contains no elements.

2.2 Mathematical properties

  • Invariant: |push| – |pop| ≥ 0 (size never negative).
  • LIFO property: for pushes x₁, x₂, …, xₙ the sequence of values returned by successive pop calls is xₙ, xₙ₋₁, …, x₁.
  • Pre‑condition for pop / peek: !isEmpty().
  • Post‑condition for push: the new element becomes the top of the stack.

2.3 Error handling

  • Under‑flow: calling pop or peek on an empty stack must raise an error (or return a sentinel such as null).
  • Overflow: only for a fixed‑size array implementation; a push when the array is full must raise an error or trigger dynamic resizing.

2.4 Complexity (worst‑case)

  • push, pop, peek, isEmptyO(1).

2.5 Implementation options

  • Array‑based – static array (fixed capacity) or dynamic array that doubles in size when full.
  • Linked‑list based – each node stores an element and a reference to the next node; the head of the list is the top of the stack.

2.6 Example pseudocode (linked‑list implementation)

class StackNode:

data

next

class Stack:

top ← null

push(x):

n ← new StackNode

n.data ← x

n.next ← top

top ← n

pop():

if top = null then error "under‑flow"

x ← top.data

top ← top.next

return x

peek():

if top = null then error "under‑flow"

return top.data

isEmpty():

return top = null

2.7 Common applications (exam‑relevant)

  • Evaluation of postfix (Reverse Polish) expressions.
  • Function‑call management – the call stack.
  • Back‑tracking problems (maze solving, puzzle solving).


3. Queue – FIFO (First‑In‑First‑Out)

3.1 Interface (operations)

  • enqueue(x) – insert element x at the rear.
  • dequeue() – remove and return the front element.
  • front() – return the front element without removing it.
  • isEmpty() – true iff the queue contains no elements.

3.2 Mathematical properties

  • Invariant: size ≥ 0 at all times.
  • FIFO property: for enqueues x₁, x₂, …, xₙ the sequence of values returned by successive dequeue calls is x₁, x₂, …, xₙ.
  • Pre‑condition for dequeue / front: !isEmpty().
  • Post‑condition for enqueue: the new element becomes the rear of the queue.

3.3 Error handling

  • Under‑flow: dequeue or front on an empty queue raises an error or returns a sentinel.
  • Overflow: only for a fixed‑size array implementation; an enqueue when the array is full must be handled.

3.4 Complexity (worst‑case)

  • enqueue, dequeue, front, isEmptyO(1) when a circular array or a linked list with front/rear pointers is used.

3.5 Implementation options

  • Circular array – fixed capacity, wrap‑around indices for front and rear.
  • Linked list – maintain pointers to both front and rear nodes; each node stores a single next reference.

3.6 Example pseudocode (linked‑list implementation)

class QueueNode:

data

next

class Queue:

front ← null

rear ← null

enqueue(x):

n ← new QueueNode

n.data ← x

n.next ← null

if rear = null then

front ← rear ← n

else

rear.next ← n

rear ← n

dequeue():

if front = null then error "under‑flow"

x ← front.data

front ← front.next

if front = null then rear ← null

return x

front():

if front = null then error "under‑flow"

return front.data

isEmpty():

return front = null

3.7 Common applications (exam‑relevant)

  • Task scheduling – printer queues, CPU job queues.
  • Breadth‑first search (BFS) of graphs.
  • Buffering streams of data (e.g., keyboard input, network packets).


4. Linked List – dynamic collection of nodes

4.1 Variants

  • Singly linked list – each node has a next reference only.
  • Doubly linked list – each node has next and prev references.
  • Circular linked list – the last node’s next points to the first node (singly or doubly).

4.2 Interface (core operations)

  • insert(pos, x) – place element x at position pos (0‑based index).
  • delete(pos) – remove the element at position pos.
  • search(x) – return the position of the first occurrence of x (or –1 if not found).
  • traverse() – visit each node in order (often to display or process data).
  • isEmpty() – true iff the list contains no nodes.

4.3 Mathematical properties

  • Invariant: #insert – #delete = length ≥ 0.
  • Link uniqueness: each node’s next (and prev for doubly) points to exactly one other node or null (or to the head in a circular list).
  • Pre‑conditions: for insert and delete, 0 ≤ pos ≤ length (or length‑1 for delete).
  • Post‑conditions: after insert(pos, x) the element x occupies position pos and all subsequent elements shift one position forward.

4.4 Error handling

  • Invalid position (e.g., pos < 0 or pos > length) – raise an error.
  • Attempting delete on an empty list – under‑flow error.

4.5 Complexity (worst‑case)

  • insert at head – O(1).
  • insert at arbitrary position – O(n) (must traverse to pos).
  • delete at head – O(1).
  • delete at arbitrary position – O(n).
  • searchO(n).
  • traverseO(n).

4.6 Implementation sketch (singly linked list)

class ListNode:

data

next

class SinglyLinkedList:

head ← null

size ← 0

insert(pos, x):

if pos < 0 or pos > size then error "invalid position"

n ← new ListNode; n.data ← x

if pos = 0 then

n.next ← head

head ← n

else

cur ← head

for i from 0 to pos‑2:

cur ← cur.next

n.next ← cur.next

cur.next ← n

size ← size + 1

delete(pos):

if pos < 0 or pos ≥ size then error "invalid position"

if pos = 0 then

head ← head.next

else

cur ← head

for i from 0 to pos‑2:

cur ← cur.next

cur.next ← cur.next.next

size ← size - 1

search(x):

cur ← head; index ← 0

while cur ≠ null:

if cur.data = x then return index

cur ← cur.next; index ← index + 1

return -1

traverse():

cur ← head

while cur ≠ null:

output cur.data

cur ← cur.next

isEmpty():

return size = 0

4.7 Common applications (exam‑relevant)

  • Dynamic collections where the size changes frequently.
  • Adjacency‑list representation of graphs.
  • Implementation of other ADTs – stacks and queues can be built from linked lists.


5. Choosing the appropriate ADT – worked example

Problem: Store a sequence of integers entered by a user and later retrieve them in reverse order.

  1. Stack

    • For each input n: stack.push(n).
    • When reverse order is required, repeatedly call stack.pop() until isEmpty() is true.
    • Justification (AO2): The LIFO property guarantees the required reverse sequence with a single ADT – no extra work.

  2. Queue

    • Enqueue each input: queue.enqueue(n).
    • To obtain reverse order, transfer all elements to a stack: while !queue.isEmpty() { stack.push(queue.dequeue()); } then pop.
    • Justification: A queue alone yields FIFO order; an additional stack is needed, making the stack the more direct choice.

  3. Linked List

    • Insert each new integer at the head: list.insert(0, n). The list now stores the numbers in reverse order automatically.
    • Alternatively, insert at the tail (preserving input order) and traverse the list backwards – possible only with a doubly linked list.
    • Justification: Head insertion is O(1) and yields the required order, but extra pointer management of a doubly linked list is unnecessary if only reverse retrieval is needed.


6. Comparison of Stack, Queue and Linked List

FeatureStackQueueLinked List
Access orderLIFOFIFOSequential as defined by links (head‑to‑tail; doubly allows tail‑to‑head)
Core operationspush, pop, peek, isEmptyenqueue, dequeue, front, isEmptyinsert, delete, search, traverse, isEmpty
Mathematical propertiesSize ≥ 0; LIFO invariantSize ≥ 0; FIFO invariantSize ≥ 0; each node has exactly one next (and optionally one previous) link
Typical worst‑case time‑complexityO(1) for all core operationsO(1) for all core operations (circular array or linked list)O(1) at head/tail; O(n) for arbitrary position, search, traverse
Implementation optionsArray (static/dynamic) or linked listCircular array or linked list (front & rear pointers)Node objects with next (and optionally prev); head (and tail) pointers
Memory useFixed (array) or dynamic (linked list)Fixed (array) or dynamic (linked list)Dynamic – one node per element plus link fields
Common applicationsExpression evaluation, recursion, back‑trackingTask scheduling, BFS, buffering streamsDynamic collections, adjacency lists, implementing other ADTs
Exam relevance (AO)AO1 (definition), AO2 (choose for reverse‑order problems), AO3 (compare with list/queue)AO1 (definition), AO2 (model FIFO processes), AO3 (analyse circular array vs linked list)AO1 (definition of nodes/links), AO2 (design a dynamic collection), AO3 (compare O(1) head insert vs O(n) arbitrary insert)


7. Formal notation (optional for higher‑level study)

Let S be a stack, Q a queue and L a singly linked list.

Stack

\[

\begin{aligned}

\text{push}(S, x) & = S \cup \{(top+1, x)\} \\

\text{pop}(S) & =

\begin{cases}

(S', x) & \text{if } S = S' \cup \{(top, x)\} \\

\text{error} & \text{if } S = \emptyset

\end{cases}

\end{aligned}

\]

Queue

\[

\begin{aligned}

\text{enqueue}(Q, x) & = Q \cup \{(rear+1, x)\} \\

\text{dequeue}(Q) & =

\begin{cases}

(Q', x) & \text{if } Q = \{(front, x)\} \cup Q' \\

\text{error} & \text{if } Q = \emptyset

\end{cases}

\end{aligned}

\]

Singly linked list

\[

\begin{aligned}

\text{insert}(L, pos, x) & = L \cup \{(pos, x)\} \\

\text{delete}(L, pos) & =

\begin{cases}

L' & \text{if } (pos, x) \in L \text{ and } L' = L \setminus \{(pos, x)\} \\

\text{error} & \text{if } pos \notin \text{valid positions}

\end{cases}

\end{aligned}

\]


8. A‑Level extensions – deeper ADT concepts

Extension topicKey ideasTypical exam task (AO)
Priority queue (heap‑based)Elements have a priority; dequeue removes the highest‑priority element. Often implemented with a binary heap (array).AO2 – design a priority‑queue for a hospital triage system; AO3 – compare O(log n) heap vs O(n) linked‑list implementation.
Deque (double‑ended queue)Supports insertion and removal at both front and rear (addFront, addRear, removeFront, removeRear).AO2 – choose a deque for a palindrome‑checking algorithm.
Circular linked listLast node points back to the first; useful for round‑robin scheduling.AO1 – define the structure; AO2 – write pseudocode for a round‑robin CPU scheduler.
Self‑adjusting lists (e.g., move‑to‑front heuristic)After a successful search, the accessed node is moved to the head to improve future access time.AO3 – analyse average‑case performance compared with a standard linked list.
Concurrent (thread‑safe) stacks/queuesUse of locks or lock‑free algorithms to allow multiple threads to operate safely.AO2 – outline a simple lock‑based solution for a shared stack.

Summary of A‑Level extensions

  • All A‑Level topics build on the core ADTs covered in sections 2‑4.
  • When answering exam questions, always start by stating the relevant ADT, its invariants and then justify why it meets the problem requirements (AO2).
  • Where the question asks for analysis, compare time‑complexity and memory usage against the basic implementations (AO3).


9. Quick revision checklist (exam‑ready)

  • Define abstract data type and differentiate interface vs implementation.
  • List the core operations, invariants and typical complexities for stack, queue and linked list.
  • Be able to sketch pseudocode for an array‑based and a linked‑list‑based implementation of each ADT.
  • Identify which ADT is most suitable for a given real‑world scenario (e.g., undo feature → stack; printer jobs → queue; dynamic collection → linked list).
  • Recall A‑Level extensions (priority queue, deque, circular list, self‑adjusting list, concurrent structures) and the key reasons they are introduced.
  • Know the AO mapping: definition → AO1, choice/design → AO2, comparison/analysis → AO3.