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.
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.
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.
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 () 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 (commonlyelement,boolean,integer,key,value). -
ops: The list of operation signatures mapping domain types (inputs before ) to codomain types (outputs after ). An operation with nothing to the left of is a constructor (initialiser) that takes no arguments and creates a fresh instance of the ADT.
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
When an operation accepts multiple inputs, their types are joined by the Cartesian product symbol (). 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(returnstrueif a bounded container has reached maximum capacity)isEmpty: ADT → bool(returnstrueif the container holds zero elements)
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 () and permit duplicates (). 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.
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 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 elements in contiguous memory cells of capacity indexed by integers . Because any memory address can be computed in arithmetic via , direct index lookup (GET) is always 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 achieve time when positional order does not need to be preserved by using the Overwrite/Swap trick, whereas Sorted Array INSERT uses one of two approaches:
- Unsorted Array INSERT at index ( Overwrite vs Shift): Move the existing element at index to the first free slot at the end of the array (
A[n] = A[i]), write the new value intoA[i] = x, and increment . This takes in BC, AC, and WC! (If positional sequence must be preserved, elements must shift right, costing ). - Unsorted Array DELETE at index ( Last-Element Swap vs Shift): Overwrite the deleted slot with the last element in the array (
A[i] = A[n - 1]) and decrement the size by 1. This avoids leaving a hole in contiguous memory in time! - Sorted Array INSERT ( via Two Equivalent HESS Methods): Either (1) run a modified Binary Search () to locate the insertion index and shift all elements from right by 1 cell (), or (2) append the new element at index in and run one pass of Insertion Sort () to bubble it left into sorted position.
| OPERATION | UNSORTED ARRAY (BC / AC / WC) | SORTED ARRAY (BC / AC / WC) | HESS MECHANISM & OVERWRITE/SHIFT DISTINCTION |
|---|---|---|---|
| GET (at index ) |
BC: AC: WC: |
BC: AC: WC: | Direct RAM offset calculation accesses in 1 step regardless of ordering. |
| SEARCH (value ) |
BC: (at ) AC: WC: ( steps) |
BC: (at midpoint) AC: WC: | Unsorted requires linear scan; sorted array enables Binary Search, halving the search interval each step. |
| INSERT (at index ) | Swap trick: (BC/AC/WC) Order-preserving shift: BC (end), WC |
BC: (at end) AC: WC: ( shifts) | Unsorted sets A[n] = A[i]; A[i] = x in . Sorted finds position in but must shift up to elements right (). |
| DELETE (at index ) | Swap trick: (BC/AC/WC) Order-preserving shift: BC (end), WC |
BC: (at end) AC: WC: ( shifts) | Unsorted replaces deleted item with last element (A[i] = A[n-1]; n--) in . Sorted must shift subsequent elements left by 1 cell (). |
stacks (lifo) & queues (fifo).
Stacks and Queues restrict where elements can be inserted and removed, guaranteeing time complexity for their primary operations when properly implemented.
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: Returnstrueif the stack contains zero elements.
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: Returnstrueif the queue is empty.
name: stack
import: element, boolean
ops:
newStack: -> stack
push: stack x element -> stack
pop: stack -> stack
peek: stack -> element
isEmpty: stack -> boolean 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 ondequeuedegrades to . Instead, a Circular Array Queue initialises two indicesfront = -1andback = -1. Whenenqueueadvancesbackpast the end of the array (back + 1 > n - 1) and free slots exist at the start due to prior dequeues,backwraps around to index0(modulo ). Similarly, whenfront > n - 1,frontwraps around to0. This guarantees worst-case time for bothenqueueanddequeue. - Linked List Symmetry (Same End vs Opposite Ends): In a pointer-based Linked List maintaining
headandtailpointers:
• Stack (LIFO): Inserts (push) and deletes (pop) at the same end—specifically athead/headin time (ortail/tailif doubly linked).
• Queue (FIFO): Inserts (enqueue) and deletes (dequeue) at opposite ends—specifically inserting attailand deleting fromheadin time (orhead/tailin a doubly linked list).
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 ( when ). Standard binary set operations include Union (), Intersection (), and Set Difference ().
A Dictionary (also called an Associative Array or Map) stores a collection of 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.
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 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 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.
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 elements are inserted into the PQ and then repeatedly extracted until empty:
| UNDERLYING DATA STRUCTURE | INSERT (ENQUEUE) | PEEK (FIND MIN/MAX) | PULL / EXTRACT MIN/MAX | SORTING ANALOGUE ( INSERTS + EXTRACTS) |
|---|---|---|---|---|
| Unsorted Array / List | Selection Sort (): Fast insertion, followed by linear scans of to select the minimum each pass. | |||
| Sorted Array / List | (BC, AC & WC) | (from end) | Insertion Sort (): Note that Enqueue via linear scan + shift is 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 ().
| |
| Binary Heap (Complete Binary Tree) | Heapsort (): Balances insertion (sift-up) and extraction (sift-down) in logarithmic time via a heap-ordered tree stored in an array. |
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., ). 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., ). 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.)
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 minimum extractions and up to priority updates (requeue / decreaseKey). With an unsorted array, each of the extractions scans all vertices ( total). With a Binary Min-Heap, both extraction and priority updates take , reducing total running time on sparse graphs () to .