COMP2521
Data Structures & Algorithms
Week 2.2
Abstract Data Types (ADTs)
Author: Sim Mautner 2023
Adapted from slides by: Hayden Smith 2021
https://slides.com/haydensmith/comp2521-21t2-2-2
In this lecture
Why?
- ADTs are a fundamental concept of writing robust software, and of being able to work with other people
What?
- ADT definition
- ADT usage
- ADT implementation
ADTs
- Data type:
- Set of values (atomic or structured)
- Collection of operations on those values
Can you think of some examples?
What is a data type?
- int
- set of value(s): an integer
- operations: addition, subtraction, multiplication, etc
- array:
- set of values(s): a repeat of any data type (e.g. int)
- operations: index lookup, index assignment, etc
Abstraction
- Abstraction: Hiding details of a how a system is built in favour of focusing on the high level behaviours, or inputs and outputs, of the system
- Examples?
- C abstracts away assembly/MIPS code.
- Python abstract away pointer arithmetic and memory allocation.
- Web browsers abstract away the underlying hardware that they're run on.
Abstract Data Type
ADT is a description of a data type that focuses on it's high level behaviour, without regard for how it is implemented underneath. This means:
- There is a separation of interface from implementations
- Users of the ADT see only the interface
- Builds of the ADT provide an implementation
- Both parties need to agree on the ADTs interface
- Interface allows people to agree at the start, and work separately.
Programming by Contract
- When we define our interface, we also need to include information about:
- Pre-conditions: What conditions hold at the start of the function call
- Post-conditions: What conditions will hold at the end of the function
- Add them via comments
- Can sanity check with asserts
Abstract Data Type
- Step 1: Determine the interface of your ADT in a .h file
- Step 2: The "developer" builds a concrete implementation for the adt in a .c file
-
Step 3: The "user" uses the abstract data type in their program
- They have to compile with it, even though they might not understand how it is built underneath
List data type: ordered collection of integer values.
What will we figure out first?
- What behaviour does this ADT need? (interface)
How are we going to code for it? (implementation)
List ADTs
- create an empty collection
-
add one item to the collection
- to the start of the list
- to the end of the list
-
remove one item from the collection
- from the start of the list
- from the end of the list
List ADTs
Let's brainstorm!
- find an item in the collection
- check the size of the collection
- drop the entire collection
- display the collection
Now we start to write this as C code!
Notice that we aren't implementing anything yet?
List ADTs
List ListCreate() // create a new list
void ListAddStart(List, int) // add number to the start
void ListAddEnd(List, int) // add number to the end
void ListDeleteStart(List) // remove number from start
void ListDeleteEnd(List) // remove number from end
int ListMember(List, int) // membership test
int ListSize(List) // size of list
void ListDestroy(List) // destroy a created listList ADTs
#ifndef LIST_H
#define LIST_H
#include <stdio.h>
#define TRUE 1
#define FALSE 0
typedef struct ListRep *List;
// ADT functions go here
#endifThree key principles of ADTs in C:
- The "List" (or equivalent) is usually a pointer of some sort
- That pointer is usually the first argument in every ADT function, with the exception of the create function
- When we write .h files, we use header guards to prevent re-definition
Notice how we haven't defined "struct List"? That's not our job.
List.h
List ADTs
// List.h ... interface to List ADT
#ifndef LIST_H
#define LIST_H
#include <stdio.h>
#define TRUE 1
#define FALSE 0
typedef struct ListRep *List;
List ListCreate(); // create a new list
void ListAddStart(List, int); // add number to the start
void ListAddEnd(List, int); // add number to the end
void ListDeleteStart(List); // remove number from start
void ListDeleteEnd(List); // remove number from end
int ListMember(List, int); // membership test
int ListSize(List); // size of list
void ListDestroy(List); // destroy a created list
List ListCopy(List); // make a copy of a set
void ListShow(List); // display set on stdout
#endifCompleted List.h
But what's missing?
Programming by contract
- Pre and post conditions now added.
- Helps both developers and users manage expectations
List ADTs
// create new empty list
// pre:
// post: Valid list returned, list is empty
List ListCreate();
// add value to the start of the list
// pre: Valid list provided
// post: New element "newval" is now at the start of list l,
// the rest of list l remains unchanged
void ListAddStart(List l, int newval);
// list size
// pre: Valid list provided for l
// post: Response is the number of elements in the list
int ListSize(List l);List Usage
- How do we actually work with a set though?
- We write our "main" file, and compile it with the set library that the ADT developer has implemented.
- While we need their .c file to build with, we never need to look at it or make sense of it, because we have the ADT (i.e. .h file)
- In fact, we could even just work with the .o file!
my_list.c
List.c
my_list.o
List.o
./my_list
List.h
List Usage
#include "List.h"
#include <stdio.h>
int main() {
List l = ListCreate();
// Could use Scanf instaed
for (int i = 1; i < 26; i += 2) {
ListAddEnd(l,i);
}
ListShow(l);
printf("\n");
}testList1.c
It's time to implement the list! The "user" of our list doesn't need to worry about this.
We will implement 3 different types of list:
- That uses an array
- That uses a simple linked list (like in COMP1511)
- That uses a doubly linked list with:
- A node holding pointers to the first node (head) and last node (tail) of the list, and a count of the number of nodes in the list
- Each node linked to both the next element as well as the previous element
List Implementation
List Implementation (array)
#include "List.h"
#define MAX_ELEMS 10000
// concrete data structure
struct ListRep {
int elems[MAX_ELEMS];
int nelems;
};
List ListCreate() {...}
void ListAddStart(List, int) {...}
void ListAddEnd(List, int) {...}
void ListDeleteStart(List) {...}
void ListDeleteEnd(List) {...}
int ListMember(List, int) {...}
int ListSize(List) {...}
void ListDestroy(List) {...}
List ListCopy(List) {...}
void ShowList(List) {...}List-array.c
We can represent this set using an array. This means we do have to do upper and lower bounds checks because there will be a theoretical limit on the size of the set.
A sample of the implemented list
List Implementation (array)
// create new empty list
List ListCreate()
{
List l = malloc(sizeof(struct ListRep));
if (l == NULL) {
fprintf(stderr, "Insufficient memory\n");
exit(1);
}
l->nelems = 0;
// assert(isValid(s));
return l;
}
// list membership test
int ListMember(List l, int n)
{
// assert(isValid(s));
int i;
for (i = 0; i < l->nelems; i++) {
if (l->elems[i] == n) {
return TRUE;
}
}
return FALSE;
}List Implementation (array)
| Data Structure | addStart/ deleteStart (time) |
insertEnd/ deleteEnd (time) |
member (time) |
storage (space) |
|---|---|---|---|---|
| array | O(n) | O(1) | O(n) | O(E) |
Let's look at the time and space complexities:
- n: Number of elements in the set
- E: Maximum number of items able to be in set
- Linked List
- No extra information
- The head of the list is passed around
- Each node links only to the next node
List Implementation (Simple Linked List)
List Implementation (Simple Linked List)
| Data Structure | addStart/ deleteStart (time) |
insertEnd/ deleteEnd (time) |
member (time) |
storage (space) |
|---|---|---|---|---|
| array | O(n) | O(1) | O(n) | O(E) |
| simple linked list | O(1) | O(n) | O(n) | O(n) |
List Implementation (Doubly Linked List)
// concrete data structure
typedef struct Node {
int value;
struct Node *next;
struct Node *prev;
} Node;
struct ListRep {
Node * head; // pointer to the first node
Node * tail; // pointer to the last node
int nelems; // number of elements
};list-doubly-linked.c
List Implementation (Doubly linked list)
// list membership test
int ListMember(List l, int n)
{
// assert(isValid(s));
Node * curr = l->head;
while (curr != NULL) {
if (curr->value == n) {
return TRUE;
}
curr = curr->next;
}
return FALSE;
}// create new empty list
List ListCreate()
{
List l = malloc(sizeof(struct ListRep));
if (l == NULL) {
fprintf(stderr, "Insufficient memory\n");
exit(EXIT_FAILURE);
}
l->head = NULL;
l->tail = NULL;
l->nelems = 0;
// assert(isValid(l));
return l;
}
Set Implementation (Linked list)
| Data Structure | addStart/ deleteStart (time) |
insertEnd/ deleteEnd (time) |
member (time) |
storage (space) |
|---|---|---|---|---|
| array | O(n) | O(1) | O(n) | O(E) |
| simple linked list | O(1) | O(n) | O(n) | O(n) |
| doubly linked list | O(1) | O(1) | O(n) | O(n) |
Direct access - issues?
What happens if we try to access elements of the implementation directly?
We might receive a "dereferencing pointer to incomplete type" error

Benefits of Restricting Access
- Allows design by contract
- Keeps the data structure in a valid state (assuming the ADT is well tested, it cannot be manipulated from outside the .c file, in unauthorised ways)
... and often importantly...
- Restricts unauthorised access and manipulation
Example 1:
- Deck of cards: When created, it is shuffled. After that cards can only be removed from the top (and possibly inserted at the bottom).
Benefits of Restricting Access
Example 2:
- In a role-playing game: A player can only buy from the shop if they have enough money to afford the item.
Benefits of Restricting Access
What other examples can you think of?
Benefits of Restricting Access
Stack:
- A list of items which only allows items to be added and removed in a first-in-last-out (FILO) fashion.
- Functions:
- create
- push (add to the top of the stack)
- pop (remove from the top of the stack)
- destroy
Some Simple Data Structures
Stack Examples:
- Plates in a cafeteria
- Wikipedia tabs: The oldest topic is of least interest now, so we only want to return to it once we have exhausted all our more recent discoveries.
Some Simple Data Structures
Queue:
- A list of items which only allows items to be added and removed in a first-in-first-out (FIFO) fashion.
- Functions:
- create
- enqueue (add an item to the back of the queue)
- dequeue (remove an item from the front of the queue)
- destroy
Some Simple Data Structures
Queue Examples:
- Waiting in line
- Turn taking in a game
- Don't want people to be able to "jump the queue", so there's no "pop" function.
Some Simple Data Structures
ADT Summary
- ADT interface:
- A user-view of the data structure
- Functions for all operations
- Explanations of those operations
- Any guarantees it provides ("Contract")
- ADT implementation:
- Concrete definition of the data structures
- List, tree, graph, array, etc etc
- Definition of functions that operate on the data structure
- Concrete definition of the data structures
- Why abstract the data structure?
- Allows future iterations to remove or upgrade a data structure
- Allows things like lists to actually have more intelligent implementations underneath
- Restricts behaviour to a finite, specified, set of actions
ADT Summary
COMP2521 23T2 - 2.2 - Abstract Data Types (ADTs)
By Sim Mautner
COMP2521 23T2 - 2.2 - Abstract Data Types (ADTs)
- 993