❖ Week 4 |
❖ Nerds You Should Know |

What he invented affects your life every single day …
❖ Nerds You Should Know (cont) |
|
Sir Tim Berners-Lee
|
|
❖ Nerds You Should Know (cont) |

webFrom that idea came the basic components still used today:
❖ Graphs |
Many applications require
❖ Graphs (cont) |
A graph G = (V,E)
❖ Graphs (cont) |
A real example: Australian road distances
| Distance | Adelaide | Brisbane | Canberra | Darwin | Melbourne | Perth   | Sydney |
| Adelaide | - | 2055 | - | 3051 | 732 | 2716 | - |
| Brisbane | 2055 | - | - | 3429 | 1671 | - | 982 |
| Canberra | - | - | - | - | 658 | - | 309 |
| Darwin | 3051 | 3429 | - | - | - | 4049 | - |
| Melbourne | 732 | 1671 | 658 | - | - | - | 873 |
| Perth | 2716 | - | - | 4049 | - | - | - |
| Sydney | - | 982 | 309 | - | 873 | - | - |
Notes: vertices are cities, edges are distance between cities, symmetric
❖ Graphs (cont) |
Alternative representation of above:
The table is good for storing data systematically. The graph diagram is good for seeing structure quickly:
❖ Graphs (cont) |
Questions we might ask about a graph:
❖ Properties of Graphs |
For now, consider a graph with no (a,a) and no direction, no weight
Terminology: |V| and |E| (cardinality) normally written just as V and E.A graph with V vertices has at most V(V-1)/2 edges.
The ratio E:V can vary considerably.
Knowing whether a graph is sparse or dense is important
❖ Exercise: Number of Edges |
The edges in a graph represent pairs of connected vertices. A graph with V vertices has V2 such pairs.
Consider V = {1,2,3,4,5} with all possible pairs:
E = { (1,1), (1,2), (1,3), (1,4), (1,5), (2,1), (2,2), …, (4,5), (5,5) }
Why do we say that the maximum #edges is V(V-1)/2?
❖ Graph Terminology |
For an edge e that connects vertices v and w
❖ Graph Terminology (cont) |
Path: a sequence of vertices where
❖ Graph Terminology (cont) |
Connected graph
❖ Graph Terminology (cont) |
Tree: connected (sub)graph with no cycles
Spanning tree: tree containing all vertices
Clique: complete subgraph
Consider the following single graph:
This graph has 26 vertices, 33 edges, and 4 connected components
Note: The entire graph has no spanning tree; what is shown in green is a spanning tree of the third connected component
❖ Graph Terminology (cont) |
A spanning tree of connected graph G = (V,E)
non-connected
❖ Exercise: Spanning Tree |
❖ Exercise: Spanning Tree (cont) |
❖ Graph Terminology |
Undirected graph
❖ Graph Terminology (cont) |
Other types of graphs …
Weighted graph
f()g()
❖ Graph Representations |
Defining graphs:
❖ Graph Representations (cont) |
We will discuss three different graph data structures:
❖ Array-of-edges Representation |
Edges are represented as an array of Edge
EdgeEdge
For simplicity, we always assume vertices to be numbered 0..V-1
❖ Array-of-edges Representation (cont) |
Graph initialisation (non-directed graph)
newGraph(V): | Input number of nodes V | Output new empty graph | | g.nV = V // #vertices (numbered 0..V-1) | g.nE = 0 // #edges | allocate enough memory for g.edges[] | return g
How much is enough? … No more than V(V-1)/2 … Much less in practice (sparse graph)
❖ Array-of-edges Representation (cont) |
Edge insertion
insertEdge(g,(v,w)):
| Input graph g, edge (v,w) // assumption: (v,w) not in g
|
| g.edges[g.nE]=(v,w)
| g.nE=g.nE+1
❖ Array-of-edges Representation (cont) |
Edge removal
removeEdge(g,(v,w)): | Input graph g, edge (v,w) // assumption: (v,w) in g | | i=0 | while (v,w)≠g.edges[i] do | i=i+1 | end while | g.edges[i]=g.edges[g.nE-1] // replace (v,w) by last edge in array | g.nE=g.nE-1
❖ Cost Analysis |
Storage cost: O(E) (big enough for # of edges)
Cost of operations:
❖ Exercise: Array-of-edges Representation |
Assuming an array-of-edges representation …
Write an algorithm to output all edges of the graph
show(g): | Input graph g | | for all i=0 to g.nE-1 do | print g.edges[i] | end for
Time complexity: O(E)
❖ Adjacency Matrix Representation (cont) |
Advantages
❖ Adjacency Matrix Representation (cont) |
Graph initialisation
newGraph(V): | Input number of nodes V | Output new empty graph | | g.nV = V // #vertices (numbered 0..V-1) | g.nE = 0 // #edges | allocate memory for g.edges[][] | for all i,j=0..V-1 do | g.edges[i][j]=0 // false | end for | return g
❖ Adjacency Matrix Representation (cont) |
Edge insertion
insertEdge(g,(v,w)): | Input graph g, edge (v,w) | | if g.edges[v][w]=0 then // (v,w) not in graph | g.edges[v][w]=1 // set to true | g.edges[w][v]=1 | g.nE=g.nE+1 | end if
❖ Adjacency Matrix Representation (cont) |
Edge removal
removeEdge(g,(v,w)): | Input graph g, edge (v,w) | | if g.edges[v][w]≠0 then // (v,w) in graph | g.edges[v][w]=0 // set to false | g.edges[w][v]=0 | g.nE=g.nE-1 | end if
❖ Exercise: Show Graph |
Assuming an adjacency matrix representation (undirected graph) …
Write an algorithm to output all edges of the graph (no duplicates!)
❖ Exercise: Show Graph (cont) |
show(g): | Input graph g | | for all i=0 to g.nV-2 do | | for all j=i+1 to g.nV-1 do | | if g.edges[i][j] then | | print i"—"j | | end if | | end for | end for
Time complexity: O(V2)
❖ Exercise: Adjacency Matrix Cost Analysis |
Analyse storage cost and time complexity of adjacency matrix representation
❖ Exercise: Adjacency Matrix Cost Analysis (cont) |
Analyse storage cost and time complexity of adjacency matrix representation
Storage cost: O(V2)
Cost of operations:
❖ Exercise: Adjacency Matrix Cost Analysis (cont) |
A storage optimisation: store only top-right part of matrix.
New storage cost: V-1 int ptrs + V(V-1)/2 ints (but still O(V2))
Requires us to always use edges (v,w) such that v < w.
❖ Adjacency List Representation (cont) |
Advantages
❖ Adjacency List Representation (cont) |
Graph initialisation
newGraph(V): | Input number of nodes V | Output new empty graph | | g.nV = V // #vertices (numbered 0..V-1) | g.nE = 0 // #edges | allocate memory for g.edges[] | for all i=0..V-1 do | g.edges[i]=NULL // empty list | end for | return g
❖ Adjacency List Representation (cont) |
Edge insertion:
insertEdge(g,(v,w)): | Input graph g, edge (v,w) | | insertLL(g.edges[v],w) | insertLL(g.edges[w],v) | g.nE=g.nE+1
❖ Adjacency List Representation (cont) |
Edge removal:
removeEdge(g,(v,w)): | Input graph g, edge (v,w) | | deleteLL(g.edges[v],w) | deleteLL(g.edges[w],v) | g.nE=g.nE-1
❖ Exercise: Adjacency List Cost Analysis |
Analyse storage cost and time complexity of adjacency list representation
❖ Exercise: Adjacency List Cost Analysis (cont) |
Analyse storage cost and time complexity of adjacency list representation
Storage cost: O(V+E) (V list pointers, total of 2·E list elements)
Cost of operations:
If vertex lists are sorted
❖ Comparison of Graph Representations |
| array of edges | adjacency matrix | adjacency list | |
| space usage | E | V2 | V+E |
| initialise | 1 | V2 | V |
| insert edge | 1 | 1 | 1 |
| find/delete edge | E | 1 | V |
Other operations:
| array of edges | adjacency matrix | adjacency list | |
| disconnected(v)? | E | V | 1 |
| isPath(x,y)? | E·log V | V2 | V+E |
| copy graph | E | V2 | V+E |
| destroy graph | 1 | V | V+E |
❖ Graph ADT |
Data:
intItem
❖ Graph ADT (cont) |
Graph ADT interface Graph.h
// graph representation is hidden typedef struct GraphRep *Graph; // vertices denoted by integers 0..N-1 typedef int Vertex; // edges are pairs of vertices (end-points) typedef struct Edge { Vertex v; Vertex w; } Edge; // operations on graphs Graph newGraph(int V); // new graph with V vertices int numOfVertices(Graph); // get number of vertices in a graph void insertEdge(Graph, Edge); void removeEdge(Graph, Edge); bool adjacent(Graph, Vertex, Vertex); /* is there an edge between two vertices */ void showGraph(Graph); // print all edges in a graph void freeGraph(Graph);
❖ Exercise: Graph ADT Client |
Write a program that uses the graph ADT to
1
#include <stdio.h>
#include "Graph.h"
#define NODES 4
#define NODE_OF_INTEREST 1
int main(void) {
Graph g = newGraph(NODES);
Edge e;
e.v = 0; e.w = 1; insertEdge(g,e);
e.v = 0; e.w = 3; insertEdge(g,e);
e.v = 1; e.w = 3; insertEdge(g,e);
e.v = 3; e.w = 2; insertEdge(g,e);
int v;
for (v = 0; v < NODES; v++) {
if (adjacent(g, v, NODE_OF_INTEREST))
printf("%d\n", v);
}
freeGraph(g);
return 0;
}
❖ Graph ADT (Array of Edges) |
Implementation of GraphRep
typedef struct GraphRep {
Edge *edges; // array of edges
int nV; // #vertices (numbered 0..nV-1)
int nE; // #edges
int n; // size of edge array
} GraphRep;
❖ Graph ADT (Adjacency Matrix) |
Implementation of GraphRep
typedef struct GraphRep {
int **edges; // adjacency matrix
int nV; // #vertices
int nE; // #edges
} GraphRep;
❖ Graph ADT (Adjacency Matrix) (cont) |
Implementation of graph initialisation (adjacency-matrix representation)
Graph newGraph(int V) {
assert(V >= 0);
int i;
Graph g = malloc(sizeof(GraphRep)); assert(g != NULL);
g->nV = V; g->nE = 0;
// allocate memory for each row
g->edges = malloc(V * sizeof(int *)); assert(g->edges != NULL);
// allocate memory for each column and initialise with 0
for (i = 0; i < V; i++) {
g->edges[i] = calloc(V, sizeof(int)); assert(g->edges[i] != NULL);
}
return g;
}
standard library function calloc(size_t nelems, size_t nbytes)
nelems*nbytes
❖ Graph ADT (Adjacency Matrix) (cont) |
Implementation of edge insertion/removal (adjacency-matrix representation)
// check if vertex is valid in a graph bool validV(Graph g, Vertex v) { return (g != NULL && v >= 0 && v < g->nV); } void insertEdge(Graph g, Edge e) { assert(g != NULL && validV(g,e.v) && validV(g,e.w)); if (!g->edges[e.v][e.w]) { // edge e not in graph g->edges[e.v][e.w] = 1; g->edges[e.w][e.v] = 1; g->nE++; } } void removeEdge(Graph g, Edge e) { assert(g != NULL && validV(g,e.v) && validV(g,e.w)); if (g->edges[e.v][e.w]) { // edge e in graph g->edges[e.v][e.w] = 0; g->edges[e.w][e.v] = 0; g->nE--; } }
❖ Exercise: Checking Neighbours (i) |
Assuming an adjacency-matrix representation …
Implement a function to check whether two vertices are directly connected by an edge
bool adjacent(Graph g, Vertex x, Vertex y) { … }
bool adjacent(Graph g, Vertex x, Vertex y) {
assert(g != NULL && validV(g,x) && validV(g,y));
return (g->edges[x][y] != 0);
}
❖ Graph ADT (Adjacency List) |
Implementation of GraphRep
typedef struct GraphRep {
Node **edges; // array of lists
int nV; // #vertices
int nE; // #edges
} GraphRep;
typedef struct Node {
Vertex v;
struct Node *next;
} Node;
❖ Exercise: Checking Neighbours (ii) |
Assuming an adjacency list representation …
Implement a function to check whether two vertices are directly connected by an edge
bool adjacent(Graph g, Vertex x, Vertex y) { … }
bool adjacent(Graph g, Vertex x, Vertex y) {
assert(g != NULL && validV(g,x));
return inLL(g->edges[x], y);
}
inLL()
❖ Problems on Graphs |
What kind of problems do we want to solve on/via graphs?
❖ Graph Algorithms |
In this course we examine algorithms for
❖ Finding a Path |
Questions on paths:
❖ Finding a Path (cont) |
Comparison of BFS/DFS search for checking if there is a path from a to h …
Both approaches ignore some edges by remembering previously visited vertices.
❖ Depth-first Search |
Depth-first search can be described recursively as
depthFirst(G,v):
v(v,w)wdepthFirst(w)The recursion induces backtracking
❖ Depth-first Search (cont) |
Recursive DFS path checking
hasPath(G,src,dest): | Input graph G, vertices src,dest | Output true if there is a path from src to dest in G, | false otherwise | | mark all vertices in G as unvisited | return dfsPathCheck(G,src,dest) dfsPathCheck(G,v,dest): | mark v as visited | if v=dest then // found dest | return true | else | | for all (v,w)∈edges(G) do | | | if w has not been visited then | | | | if dfsPathCheck(G,w,dest) then | | | | return true // found path via w to dest | | | | end if | | | end if | | end for | end if | return false // no path from v to dest
❖ Exercise: Depth-first Traversal (i) |
Trace the execution of dfsPathCheck(G,0,5)
Consider neighbours in ascending order
❖ Exercise: Depth-first Traversal (i) (cont) |
Cost analysis:
❖ Exercise: Depth-first Traversal (i) (cont) |
Note how different graph data structures affect cost:
In case of Adjacency List:
For dense graphs … E ≅ V2 ⇒ O(V+E) = O(V2)
For sparse graphs … E ≅ V ⇒ O(V+E) = O(E)
❖ Exercise: Depth-first Traversal (i) (cont) |
Knowing whether a path exists can be useful
Knowing what the path is even more useful
⇒ record the previously visited node as we search through the graph (so that we can then trace path through graph)
Make use of global variable:
visited[]
❖ Exercise: Depth-first Traversal (i) (cont) |
visited[] // store previously visited node, for each vertex 0..nV-1 findPath(G,src,dest): | Input graph G, vertices src,dest | | for all vertices v∈G do | visited[v]=-1 | end for | visited[src]=src // starting node of the path | if dfsPathCheck(G,src,dest) then // show path in dest..src order | | v=dest | | while v≠src do | | print v"-" | | v=visited[v] | | end while | | print src | end if dfsPathCheck(G,v,dest): | if v=dest then // found edge from v to dest | return true | else | | for all (v,w)∈edges(G) do | | | if visited[w]=-1 then | | | | visited[w]=v | | | | if dfsPathCheck(G,w,dest) then | | | | return true // found path via w to dest | | | | end if | | | end if | | end for | end if | return false // no path from v to dest
❖ Exercise: Depth-first Traversal (ii) |
Show the DFS order in which we visit vertices in this graph when searching for a path from 0 to 6:
Consider neighbours in ascending order
❖ Exercise: Depth-first Traversal (ii) (cont) |
DFS can also be described non-recursively (via a stack):
hasPath(G,src,dest): | Input graph G, vertices src,dest | Output true if there is a path from src to dest in G, | false otherwise | | mark all vertices in G as unvisited | push src onto new stack s | found=false | while not found and s is not empty do | | pop v from s | | mark v as visited | | if v=dest then | | found=true | | else | | | for each (v,w)∈edges(G) such that w has not been visited | | | push w onto s | | | end for | | end if | end while | return found
Uses standard stack operations (push, pop, check if empty)
Time complexity is the same: O(V+E)
❖ Exercise: Depth-first Traversal (iii) |
Show how the stack evolves when executing findPathDFS(g,0,5)
Push neighbours in descending order … so they get popped in ascending order
❖ Breadth-first Search |
Basic approach to breadth-first search (BFS):
❖ Breadth-first Search (cont) |
BFS algorithm (records visiting order, marks vertices as visited when put on queue):
visited[] // array of visiting orders, indexed by vertex 0..nV-1
findPathBFS(G,src,dest):
| Input graph G, vertices src,dest
|
| for all vertices v∈G do
| visited[v]=-1
| end for
| enqueue src into new queue q
| visited[src]=src
| found=false
| while not found and q is not empty do
| | dequeue v from q
| | if v=dest then
| | found=true
| | else
| | | for each (v,w)∈edges(G) such that visited[w]=-1 do
| | | enqueue w into q
| | | visited[w]=v
| | | end for
| | end if
| end while
| if found then
| display path in dest..src order
| end if
Uses standard queue operations (enqueue, dequeue, check if empty)
❖ Exercise: Breadth-first Traversal |
Show the BFS order in which we visit vertices in this graph when searching for a path from 0 to 6:
Consider neighbours in ascending order
❖ Exercise: Breadth-first Traversal (cont) |
Time complexity of BFS: O(V+E) (adjacency list representation, same as DFS)
BFS finds a "shortest" path
In many applications, edges are weighted and we want path
❖ Other DFS Examples |
Other problems to solve via DFS graph search
❖ Exercise: Buggy Cycle Check |
A graph has a cycle if
The following DFS cycle check has two bugs. Find them.
hasCycle(G): | Input graph G | Output true if G has a cycle, false otherwise | | choose any vertex v∈G | return dfsCycleCheck(G,v) dfsCycleCheck(G,v): | mark v as visited | for each (v,w)∈edges(G) do | | if w has been visited then // found cycle | | return true | | else if dfsCycleCheck(G,w) then | | return true | end for | return false // no cycle at v
for each (v,w)∈edges(G) do
should exclude the neighbour of v from which you just came, so as to prevent a single edge w-v from being classified as a cycle.
dfsCycleCheck(G,v,parent): | mark v as visited | for each (v,w)∈edges(G) do | | if w has been visited and w is not parent then // found cycle | | return true | | else if dfsCycleCheck(G,w,parent) then | | return true | end for | return false // no cycle at v
❖ Computing Connected Components |
Problems:
componentOf[]
❖ Computing Connected Components (cont) |
Algorithm to assign vertices to connected components:
components(G): | Input graph G | | for all vertices v∈G do | componentOf[v]=-1 | end for | compID=0 | for all vertices v∈G do | | if componentOf[v]=-1 then | | dfsComponents(G,v,compID) | | compID=compID+1 | | end if | end for dfsComponents(G,v,id): | componentOf[v]=id | for all vertices w adjacent to v do | if componentOf[w]=-1 then | dfsComponents(G,w,id) | end if | end for
❖ Exercise: Connected components |
Trace the execution of the algorithm
Consider neighbours in ascending order
| [0] | [1] | [2] | [3] | [4] | [5] | [6] | [7] |
| -1 | -1 | -1 | -1 | -1 | -1 | -1 | -1 |
| 0 | -1 | -1 | -1 | -1 | -1 | -1 | -1 |
| 0 | -1 | 0 | -1 | -1 | -1 | -1 | -1 |
| 0 | 0 | 0 | -1 | -1 | -1 | -1 | -1 |
| 0 | 0 | 0 | 1 | -1 | -1 | -1 | -1 |
| … | |||||||
| 0 | 0 | 0 | 1 | 1 | 2 | 2 | 2 |
| [0] | [1] | [2] | [3] | [4] | [5] | [6] | [7] |
| -1 | -1 | -1 | -1 | -1 | -1 | -1 | -1 |
| 0 | -1 | -1 | -1 | -1 | -1 | -1 | -1 |
| 0 | 0 | -1 | -1 | -1 | -1 | -1 | -1 |
| 0 | 0 | 0 | -1 | -1 | -1 | -1 | -1 |
| … | |||||||
| 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 |
❖ Hamiltonian Path and Circuit |
Hamiltonian path problem:
Simple to state, but difficult to solve (NP-complete)
Many real-world applications require you to visit all vertices of a graph:
❖ Hamiltonian Path and Circuit (cont) |
Approach:
❖ Hamiltonian Path and Circuit (cont) |
Algorithm for finding Hamiltonian path:
visited[] // array [0..nV-1] to keep track of visited vertices hasHamiltonianPath(G,src,dest): | for all vertices v∈G do | visited[v]=false | end for | return hamiltonR(G,src,dest,#vertices(G)-1) hamiltonR(G,v,dest,d): | Input G graph | v current vertex considered | dest destination vertex | d distance "remaining" until path found | | if v=dest then | if d=0 then return true else return false | else | | mark v as visited | | for each unvisited neighbour w of v in G do | | if hamiltonR(G,w,dest,d-1) then //recursively ask whether a valid Hamiltonian path | | return true // can be completed from w | | end if | | end for | end if | mark v as unvisited // reset visited mark | // no hamiltonian path from v in this consideration | return false
❖ Exercise: Hamiltonian Path |
Trace the execution of the algorithm when searching for a Hamiltonian path from 1 to 6:
Consider neighbours in ascending order
| 1-0-2-3-4-5-6 | d≠0 | |
| 1-0-2-3-4-5-7-8-9 | no unvisited neighbour | |
| 1-0-2-3-4-5-7-9-8 | no unvisited neighbour | |
| 1-0-2-3-4-7-5-6 | d≠0 | |
| 1-0-2-3-4-7-8-9 | no unvisited neighbour | |
| 1-0-2-3-4-7-9-8 | no unvisited neighbour | |
| 1-0-2-3-4-8-7-5-6 | d≠0 | |
| 1-0-2-3-4-8-7-9 | no unvisited neighbour | |
| 1-0-2-3-4-8-9-7-5-6 | ✓ |
Repeat on your own with src=0dest=6
❖ Exercise: Hamiltonian Path (cont) |
Analysis: worst case requires (V-1)! paths to be examined
Consider a graph with isolated vertex and the rest fully-connected
Checking hasHamiltonianPath(g,,0)
Note, however, that the above case could be solved in constant time if
we had a fast check for 0 and x being in the same connected component
❖ Euler Path and Circuit |
Euler path problem:
Many real-world applications require you to visit all edges of a graph:
❖ Euler Path and Circuit (cont) |
One possible "brute-force" approach:
Theorem. A graph has an Euler circuit if and only if
it is connected and all vertices have even degree
Theorem. A graph has a non-circuitous Euler path if and only if
it is connected and exactly two vertices have odd degree
❖ Exercise: Euler Paths and Circuits |
Which of these two graphs have an Euler path? an Euler circuit?
❖ Exercise: Euler Paths and Circuits (cont) |
Assume the existence of degree(g,v)
Algorithm to check whether a graph has an Euler path:
hasEulerPath(G,src,dest): | Input graph G, vertices src,dest | Output true if G has Euler path from src to dest | false otherwise | | if degree(G,src) is even or degree(G,dest) is even then | return false | end if | for all vertices v∈G do | if v≠src and v≠dest and degree(G,v) is odd then | return false | end if | end for | return true
The basic idea: ensure that all vertices have even degree except for the src and dest, which have odd degrees. All non-source/destination vertices with odd degrees must be connected in pairs to form an even degree.
❖ Exercise: Euler Paths and Circuits (cont) |
Analysis of hasEulerPath
For the keen, a linear-time (in the number of edges, E) algorithm to compute an Euler path is described in [Sedgewick] Ch.17.7.
❖ Summary |
Produced: 16 Mar 2026