LESSON 01 · UNIT 3 AOS 1 DATA MODELLING & ADTS

Abstract Data Types, Formal Signatures & Core Containers

An Abstract Data Type (ADT) is a mathematical model for data types where a data type is defined by its behaviour (semantics) from the point of view of a user of the data, specifically in terms of possible values, possible operations on data of this type, and the behaviour of these operations.

01 · SPECIFICATION VS IMPLEMENTATION

abstract data types & formal signatures.

In VCE Algorithmics, an ADT separates what operations are available from how they are implemented in hardware or a concrete programming language. While a Data Structure is a concrete memory layout (such as a contiguous block of RAM or pointer-linked nodes), an Abstract Data Type is a purely algebraic specification. In procedural languages (such as C), an ADT is typically stored using a struct, whereas object-oriented programming (OOP) languages (such as Java or Python) encapsulate ADTs using classes and objects.

ABSTRACT DATA TYPE (ADT) LOGICAL VIEW

Defines a domain of objects and a set of mathematical operations (constructors, mutators, accessors/queries) through a formal ADT Signature, remaining completely agnostic to memory addresses or time complexity.

CONCRETE DATA STRUCTURE PHYSICAL VIEW

Implements an ADT using concrete machine primitives (arrays, pointers, hash tables, binary heaps). The choice of underlying data structure determines the exact asymptotic time complexity (O(f(n))\mathcal{O}(f(n))) of each ADT operation.

Structure of a Formal ADT Signature

Every formal ADT specification in VCE Algorithmics consists of three mandatory sections:

  • name: The identifier of the ADT being defined (e.g., stack, queue, dictionary).
  • import: Existing primitive or composite types required to define the operations (commonly element, boolean, integer, key, value).
  • ops: The list of operation signatures mapping domain types (inputs before →\rightarrow) to codomain types (outputs after →\rightarrow). An operation with nothing to the left of →\rightarrow is a constructor (initialiser) that takes no arguments and creates a fresh instance of the ADT.
TEMPLATE · FORMAL ADT SIGNATURE SYNTAX ALGEBRAIC SPECIFICATION
name: <adt_name>
import: element, boolean, integer
ops:
  newADT:                   -> <adt_name>
  mutatorOp: <adt_name> x element -> <adt_name>
  queryOp:   <adt_name>           -> element
  isEmpty:   <adt_name>           -> boolean
CARTESIAN PRODUCT IN SIGNATURES NOTATION RULE

When an operation accepts multiple inputs, their types are joined by the Cartesian product symbol (×\times). For example, insert: list × integer × element → list takes a 3-tuple consisting of an existing list, an integer index, and an element, and returns the updated list. Across all container ADTs, three universal helper/query operations frequently appear:

  • length: ADT → int (returns the number of elements currently stored)
  • isFull: ADT → bool (returns true if a bounded container has reached maximum capacity)
  • isEmpty: ADT → bool (returns true if the container holds zero elements)
02 · SEQUENTIAL CONTAINERS

lists & arrays (unsorted vs sorted).

A List is an ordered collection of elements where the same value may appear more than once. Unlike sets, lists preserve positional sequence ((a,b)≠(b,a)(a, b) \neq (b, a)) and permit duplicates ((a,a,b)(a, a, b)). In strongly typed OOP languages like Java, a homogeneous list is declared with generics such as ArrayList<String>, whereas a heterogeneous list (permitting mixed element types) can be declared as ArrayList<Object> because every Java class inherits from the root Object class.

ADT SIGNATURE · LIST SEQUENTIAL ADT
name: list
import: element, integer, boolean
ops:
  emptyList:                       -> list
  isEmpty:   list                  -> boolean
  prepend:   list x element        -> list
  append:    list x element        -> list
  head:      list                  -> element
  tail:      list                  -> list
  length:    list                  -> integer
ADT SIGNATURE · ARRAY INDEXED CONTAINER
name: array
import: integer, element, boolean
ops:
  newArray:                         -> array
  get:      array x integer         -> element
  set:      array x integer x element -> array
  insert:   array x integer x element -> array
  delete:   array x integer         -> array
  search:   array x element         -> boolean

Array ADT: Unsorted vs Sorted Time Complexity (BC, AC, WC)

An Array stores nn elements in contiguous memory cells of capacity c≫nc \gg n indexed by integers 0…n−10 \dots n-1. Because any memory address can be computed in Θ(1)\Theta(1) arithmetic via base_addr+i×elem_size\text{base\_addr} + i \times \text{elem\_size}, direct index lookup (GET) is always Θ(1)\Theta(1) across Best Case (BC), Average Case (AC), and Worst Case (WC). Note also that arrays follow reference semantics: assigning one array variable to another (e.g., B = A) copies only the memory reference (address pointer), not the underlying array elements.

In the HESS curriculum notes, Unsorted Array INSERT and DELETE at an interior index i<ni < n achieve Θ(1)\Theta(1) time when positional order does not need to be preserved by using the Overwrite/Swap trick, whereas Sorted Array INSERT uses one of two Θ(n)\Theta(n) approaches:

  • Unsorted Array INSERT at index i<ni < n (Θ(1)\Theta(1) Overwrite vs Θ(n)\Theta(n) Shift): Move the existing element at index ii to the first free slot at the end of the array (A[n] = A[i]), write the new value into A[i] = x, and increment nn. This takes Θ(1)\Theta(1) in BC, AC, and WC! (If positional sequence must be preserved, elements i…n−1i \dots n-1 must shift right, costing O(n)\mathcal{O}(n)).
  • Unsorted Array DELETE at index i<ni < n (Θ(1)\Theta(1) Last-Element Swap vs Θ(n)\Theta(n) Shift): Overwrite the deleted slot with the last element in the array (A[i] = A[n - 1]) and decrement the size nn by 1. This avoids leaving a hole in contiguous memory in Θ(1)\Theta(1) time!
  • Sorted Array INSERT (Θ(n)\Theta(n) via Two Equivalent HESS Methods): Either (1) run a modified Binary Search (O(log⁡n)\mathcal{O}(\log n)) to locate the insertion index ii and shift all elements from i…n−1i \dots n-1 right by 1 cell (O(n)\mathcal{O}(n)), or (2) append the new element at index nn in Θ(1)\Theta(1) and run one pass of Insertion Sort (O(n)\mathcal{O}(n)) to bubble it left into sorted position.
ARRAY IMPLEMENTATION · BEST (BC), AVERAGE (AC) & WORST CASE (WC) COMPLEXITY (HESS NOTES)
OPERATION UNSORTED ARRAY (BC / AC / WC) SORTED ARRAY (BC / AC / WC) HESS MECHANISM & OVERWRITE/SHIFT DISTINCTION
GET (at index ii) BC: Θ(1)\Theta(1)
AC: Θ(1)\Theta(1)
WC: Θ(1)\Theta(1)
BC: Θ(1)\Theta(1)
AC: Θ(1)\Theta(1)
WC: Θ(1)\Theta(1)
Direct RAM offset calculation accesses A[i]A[i] in 1 step regardless of ordering.
SEARCH (value xx) BC: Θ(1)\Theta(1) (at A[0]A[0])
AC: Θ(n)\Theta(n)
WC: Θ(n)\Theta(n) (nn steps)
BC: Θ(1)\Theta(1) (at midpoint)
AC: Θ(log⁡n)\Theta(\log n)
WC: ⌊log⁡2n⌋∈Θ(log⁡n)\lfloor\log_2 n\rfloor \in \Theta(\log n)
Unsorted requires linear scan; sorted array enables Binary Search, halving the search interval each step.
INSERT (at index ii) Swap trick: Θ(1)\Theta(1) (BC/AC/WC)
Order-preserving shift: BC Θ(1)\Theta(1) (end), WC Θ(n)\Theta(n)
BC: Θ(1)\Theta(1) (at end)
AC: Θ(n)\Theta(n)
WC: Θ(n)\Theta(n) (nn shifts)
Unsorted sets A[n] = A[i]; A[i] = x in Θ(1)\Theta(1). Sorted finds position in O(log⁡n)\mathcal{O}(\log n) but must shift up to nn elements right (Θ(n)\Theta(n)).
DELETE (at index ii) Swap trick: Θ(1)\Theta(1) (BC/AC/WC)
Order-preserving shift: BC Θ(1)\Theta(1) (end), WC Θ(n)\Theta(n)
BC: Θ(1)\Theta(1) (at end)
AC: Θ(n)\Theta(n)
WC: Θ(n)\Theta(n) (n−1n-1 shifts)
Unsorted replaces deleted item with last element (A[i] = A[n-1]; n--) in Θ(1)\Theta(1). Sorted must shift subsequent elements left by 1 cell (Θ(n)\Theta(n)).
03 · RESTRICTED-ACCESS LINEAR ADTS

stacks (lifo) & queues (fifo).

Stacks and Queues restrict where elements can be inserted and removed, guaranteeing O(1)\mathcal{O}(1) time complexity for their primary operations when properly implemented.

STACK ADT · LIFO LAST-IN, FIRST-OUT

Elements are pushed onto and popped from the same end (the top of the stack). Used in function call frames (call stack), expression evaluation, backtracking state trees, and iterative Depth-First Search (DFS).

  • push: Adds an element to the top of the stack.
  • pop: Removes the top element from the stack.
  • peek / top: Returns the top element without modifying the stack.
  • isEmpty: Returns true if the stack contains zero elements.
QUEUE ADT · FIFO FIRST-IN, FIRST-OUT

Elements are enqueued at the rear (back/tail) and dequeued from the front (head). Used in task scheduling, buffer streams, and Breadth-First Search (BFS) level-order exploration.

  • enqueue: Appends an element to the rear of the queue.
  • dequeue: Removes the element at the front of the queue.
  • front / peek: Inspects the front element without removing it.
  • isEmpty: Returns true if the queue is empty.
ADT SIGNATURE · STACK LIFO SPECIFICATION
name: stack
import: element, boolean
ops:
  newStack:                 -> stack
  push:     stack x element -> stack
  pop:      stack           -> stack
  peek:     stack           -> element
  isEmpty:  stack           -> boolean
ADT SIGNATURE · QUEUE FIFO SPECIFICATION
name: queue
import: element, boolean
ops:
  newQueue:                 -> queue
  enqueue:  queue x element -> queue
  dequeue:  queue           -> queue
  front:    queue           -> element
  isEmpty:  queue           -> boolean

Concrete Implementations of Stack & Queue (Array Ring Buffer vs Linked List)

In the HESS study guide, the structural difference between a Stack and a Queue is illuminated by comparing how they manipulate array indices and linked-list pointers:

  • Circular Ring Buffer (Array Queue with front = -1, back = -1): A naive array queue that shifts elements left on dequeue degrades to Θ(n)\Theta(n). Instead, a Circular Array Queue initialises two indices front = -1 and back = -1. When enqueue advances back past the end of the array (back + 1 > n - 1) and free slots exist at the start due to prior dequeues, back wraps around to index 0 (modulo nn). Similarly, when front > n - 1, front wraps around to 0. This guarantees Θ(1)\Theta(1) worst-case time for both enqueue and dequeue.
  • Linked List Symmetry (Same End vs Opposite Ends): In a pointer-based Linked List maintaining head and tail pointers:
    • Stack (LIFO): Inserts (push) and deletes (pop) at the same end—specifically at head / head in Θ(1)\Theta(1) time (or tail / tail if doubly linked).
    • Queue (FIFO): Inserts (enqueue) and deletes (dequeue) at opposite ends—specifically inserting at tail and deleting from head in Θ(1)\Theta(1) time (or head / tail in a doubly linked list).
LIFO Stack, Circular Ring Buffer Queue, and Linked List Pointers Technical diagram comparing same-end LIFO Stack access at head/top with opposite-end FIFO Circular Ring Buffer wrap-around (front and back indices) and Linked List head/tail pointers. STACK (LIFO · SAME END) TOP: 42 19 07 push / pop at HEAD Θ(1) single-end CIRCULAR BUFFER QUEUE F B (i+1) mod n front / back (-1 init) wrap: back+1 > n-1 → 0 LINKED QUEUE (FIFO) HEAD ••• TAIL dequeue enqueue Opposite ends (tail/head) Both ops in Θ(1) time
FIGURE 01 · LIFO SINGLE-END STACK VS FIFO CIRCULAR RING BUFFER & OPPOSITE-END LINKED QUEUE
04 · UNORDERED & ASSOCIATIVE CONTAINERS

sets & dictionaries.

A Set models a mathematical finite set: an unordered collection of distinct (unique) elements. Adding an element already present in the set has no effect (S∪{x}=SS \cup \{x\} = S when x∈Sx \in S). Standard binary set operations include Union (A∪BA \cup B), Intersection (A∩BA \cap B), and Set Difference (A∖BA \setminus B).

A Dictionary (also called an Associative Array or Map) stores a collection of (key,value)(\text{key}, \text{value}) pairs such that each key appears at most once. Looking up, inserting, or deleting a value is performed by its associated key rather than an integer position.

ADT SIGNATURE · SET UNIQUE ELEMENTS
name: set
import: element, boolean
ops:
  emptySet:               -> set
  insert:     set x element -> set
  remove:     set x element -> set
  contains:   set x element -> boolean
  isEmpty:    set         -> boolean
  union:      set x set   -> set
  intersect:  set x set   -> set
  difference: set x set   -> set
ADT SIGNATURE · DICTIONARY KEY-VALUE MAPPING
name: dictionary
import: key, value, boolean
ops:
  newDict:                        -> dictionary
  put:      dictionary x key x value -> dictionary
  get:      dictionary x key      -> value
  remove:   dictionary x key      -> dictionary
  contains: dictionary x key      -> boolean
  isEmpty:  dictionary            -> boolean
05 · ORDERED EXTRACTION & HEAPS

priority queues & sorting connections.

A Priority Queue (PQ) is an abstract data type similar to a regular queue or stack, except that every element has an associated priority (key). In a Min-Priority Queue (used in Prim's, Dijkstra's, and A* search), the element with the lowest numerical key is extracted first; in a Max-Priority Queue, the element with the highest priority is extracted first. Elements with equal priority are served according to their order in the queue.

ADT SIGNATURE · PRIORITY QUEUE WEIGHTED EXTRACTION
name: priority_queue
import: element, priority, boolean
ops:
  newPQ:                                    -> priority_queue
  insert:     priority_queue x element x priority -> priority_queue
  pull:       priority_queue                -> priority_queue
  peek:       priority_queue                -> element
  requeue:    priority_queue x element x priority -> priority_queue
  isEmpty:    priority_queue                -> boolean

Concrete Implementations & Isomorphism to Sorting Algorithms

In the HESS curriculum notes, each concrete implementation of a Priority Queue directly mirrors a classic sorting algorithm when nn elements are inserted into the PQ and then repeatedly extracted until empty:

PRIORITY QUEUE IMPLEMENTATIONS · COMPLEXITY & EQUIVALENT SORTING PARADIGM
UNDERLYING DATA STRUCTURE INSERT (ENQUEUE) PEEK (FIND MIN/MAX) PULL / EXTRACT MIN/MAX SORTING ANALOGUE (nn INSERTS + nn EXTRACTS)
Unsorted Array / List O(1)\mathcal{O}(1) O(n)\mathcal{O}(n) O(n)\mathcal{O}(n) Selection Sort (Θ(n2)\Theta(n^2)): Fast O(1)\mathcal{O}(1) insertion, followed by nn linear scans of O(n)\mathcal{O}(n) to select the minimum each pass.
Sorted Array / List Θ(n)\Theta(n) (BC, AC & WC) Θ(1)\Theta(1) Θ(1)\Theta(1) (from end) Insertion Sort (O(n2)\mathcal{O}(n^2)): Note that Enqueue via linear scan + shift is Θ(n)\Theta(n) in all cases (BC, AC, and WC) because any elements not scanned during search must instead be shifted right by 1! Extraction is then immediate (Θ(1)\Theta(1)).
Binary Heap (Complete Binary Tree) O(log⁡n)\mathcal{O}(\log n) O(1)\mathcal{O}(1) O(log⁡n)\mathcal{O}(\log n) Heapsort (Θ(nlog⁡n)\Theta(n \log n)): Balances insertion (sift-up) and extraction (sift-down) in logarithmic time via a heap-ordered tree stored in an array.
TRANSFORMING A PRIORITY QUEUE INTO A STACK OR QUEUE HESS STRUCTURAL INSIGHT

A Priority Queue directly generalises both Stacks and FIFO Queues depending on how priorities are assigned upon insertion (for a Max-Priority Queue that extracts the highest priority first):

  • Priority Queue as a Stack (LIFO): Assign monotonically increasing priorities to each newly inserted element (e.g., p1<p2<p3<…p_1 < p_2 < p_3 < \dots). The most recently inserted item always has the highest priority and is extracted first (Last-In, First-Out).
  • Priority Queue as a Normal Queue (FIFO): Assign monotonically decreasing priorities to each newly inserted element (e.g., p1>p2>p3>…p_1 > p_2 > p_3 > \dots). The earliest inserted item retains the highest priority and is extracted first (First-In, First-Out). (Note: In a Min-Priority Queue, the monotonic directions simply reverse.)
06 · ACTIVE VERIFICATION

self-checks & mastery checklist.

Self-Check 1: Why do VCE ADT signatures often have two operations for removal (e.g., peek + pop) instead of returning a tuple?

In pure algebraic ADT specifications, each operation maps its input domain to a single output sort (codomain). Separating the accessor/query (peek: stack → element, which inspects the top item without mutating the container) from the mutator (pop: stack → stack, which returns the modified container without the top item) keeps every signature function single-valued and mathematically composable.

Self-Check 2: Write a formal ADT signature for a Set that supports adding an element, removing an element, checking membership, and set intersection.
name: set
import: element, boolean
ops:
  newSet:                  -> set
  add:       set x element -> set
  remove:    set x element -> set
  contains:  set x element -> boolean
  intersect: set x set     -> set
Self-Check 3: Explain why upgrading Prim's or Dijkstra's algorithm from an Unsorted Array PQ to a Binary Min-Heap PQ improves sparse graph performance.

Both Prim's and Dijkstra's algorithms perform ∣V∣|V| minimum extractions and up to ∣E∣|E| priority updates (requeue / decreaseKey). With an unsorted array, each of the ∣V∣|V| extractions scans all ∣V∣|V| vertices (O(∣V∣2)\mathcal{O}(|V|^2) total). With a Binary Min-Heap, both extraction and priority updates take O(log⁡∣V∣)\mathcal{O}(\log |V|), reducing total running time on sparse graphs (∣E∣≈∣V∣|E| \approx |V|) to O((∣V∣+∣E∣)log⁡∣V∣)=O(∣E∣log⁡∣V∣)\mathcal{O}((|V| + |E|)\log |V|) = \mathcal{O}(|E|\log |V|).