Graphs Definition of a graph
Plan of the lecture: A graph is a set of nodes, or vertices, connected by edges.
• What is a graph
D
B C
• What are they used for
• Graph problems
A E
• Two ways of implementing graphs F
• BFS and DFS, their complexity
Applications of Graphs Directed and Undirected Graphs
Graphs can be used to represent Graphs can be
• networks (e.g., of computers or roads) • undirected (edges don’t have direction)
• flow charts • directed (edges have direction)
• tasks in some project (some of which should be completed B
before others), so edges correspond to prerequisites.
D directed graph
A
• states of an automaton / program
Weighted and Unweighted
Directed and Undirected Graphs
Graphs
Undirected graphs can be represented as directed graphs Graphs can also be
where for each edge (X,Y) there is a corresponding edge
(Y,X). • unweighted (as in the previous examples)
• weighted (edges have weights)
A B C undirected graph B
10 12
A B C corresponding D
A weighted graph
directed graph 8
14
C
1
Notation Adjacency relation
• Set V of vertices (nodes) • Node B is adjacent to A if there is an edge from A to B.
• Set E of edges (E ⊆ V × V)
A B
Example: B C
V = {A, B, C}, E = {(A,B), (A,C), (B,C)}
Paths and reachability More Terminology
• A cycle is a path from a vertex to itself
• A path from A to B is a sequence of vertices A1,...,An such • Graph is acyclic if it does not have cycles
that there is an edge from A to A1, from A1 to A2, ..., from
• Graph is connected if there is a path between every pair of
An to B.
vertices
• Graph is strongly connected if there is a path in both
directions between every pair of vertices
A A1 A2 A3 A4 A5 B
• A vertex B is reachable from A if there is a path from A to
B
Applications of Graphs Some graph problems
For example, • Searching a graph for a vertex
• nodes could represent positions in a board game, and edges • Searching a graph for an edge
the moves that transform one position into another ...
• nodes could represent computers (or routers) in a network • Finding a path in the graph (from one vertex to another)
and weighted edges the bandwidth between them • Finding the shortest path between two vertices
• nodes could represent towns and weighted edges road
distances between them, or train journey times or ticket • Cycle detection
prices ...
2
More graph problems How to implement a graph
• Topological sort (finding a linear sequence of vertices As with lists, there are several approaches, e.g.:
which agrees with the direction of edges in the graph, e.g.,
for scheduling tasks in a project) • using a static indexed data structure
• Minimal spanning tree (deleting as many edges in a graph • using a dynamic data structure
as possible, so that all vertices are still connected by
shortest possible edges, e.g., in network routing or circuit
design.)
Static implementation:Adjacency
Example
Matrix
• Store node in the array: each node is associated with an B 0 1 2 3
integer (array index)
0 0 1 1 0
• Represent information about the edges using a two
dimensional array, where A D 1 0 0 0 1
2 0 0 0 1
array[i][j] == 1
3 0 0 0 0
iff there is an edge from node with index i to the node C
with index j. A B C D node indices adjacency
0 1 2 3 matrix
Weighted graphs Finding node’s index fast
• For weighted graphs, place weights in matrix (if there is no • Keeping node labels in an array is a bit awkward (have to
edge we use a value which can’t be confused with a look through the array each time to find node’s position in
weight, e.g., -1 or Integer.MAX_VALUE) the matrix)
• Alternative solution would be to use a HashMap: the node
is the key, and an Integer object holding its index is the
value.
• When we need to know the node v’s position we could do
int pos = map.get(v);
3
Disadvantages of adjacency
Adjacency List
matrices
• Sparse graphs with few edges for number of vertices result • For every vertex, keep a list of adjacent vertices.
in many zero entries in adjacency matrix—this wastes • Keep a list of vertices, or keep vertices in a HashMap as
space and makes many algorithms less efficient (e.g., to keys and lists of adjacent vertices as values.
find nodes adjacent to a given node, we have to iterate
through the whole row even if there are few 1s there).
• Also, if the number of nodes in the graph may change,
matrix representation is too inflexible (especially if we
don’t know the maximal size of the graph).
Adjacency list HashMap implementation
B nodes list of adjacent nodes
Key Value
A B, C
A D B D A Linked list containing B,C
B Linked list containing D
C D
C Linked list containing D
C D
D Empty linked list
Reading Graph traversals
• Goodrich and Tamassia (Ch. 13) have a somewhat • In this lecture, we look at two ways of visiting all vertices
different Graph implementation, where edges are in a graph: breadth-first search and depth-first search.
first-class objects. • Traversal of the graph is used to perform tasks such as
searching for a certain node
• In general, choice of implementation depends on • It can also be slightly modified to search for a path
what we want to do with a graph. between two nodes, check if the graph is connected, check
if it contains loops, and so on.
4
Breadth first search BFS starting from A:
BFS starting from vertex v:
Q={A}
Q={B,G}
D
create a queue Q B C Q={G,C,F}
mark v as visited and put v into Q
Q={C,F}
while Q is non-empty
remove the head u of Q A Q={F,D,E}
E
F
mark and enqueue all (unvisited) Q={D,E}
neighbours of u Q={E}
G Q={}
Simple DFS DFS starting from A:
DFS starting from vertex v: S={A}
S={A,B}
D
create a stack S B C S={A,B,C}
mark v as visited and push v onto S
while S is non-empty S={A,B,C,D}
peek at the top u of S A S={A,B,C}
E
if u has an (unvisited)neighbour w, F
S={A,B,C,E}
mark w and push it onto S
S={A,B,C,E,F}
else pop S
G S={A,B,C,E,F,G}
DFS starting from A (continued): Modification of depth first search
• How to get DFS to detect cycles in a directed graph:
S={A,B,C,E,F}
idea: if we encounter a vertex which is already on the stack,
S={A,B,C,E} we found a loop (stack contains vertices on a path, and if
D
B C S={A,B,C} we see the same vertex again, the path must contain a
S={A,B} cycle).
• Instead of visited and unvisited, use three colours:
A S={A}
E – white = unvisited
F
S={}
– grey = on the stack
– black = finished (we backtracked from it, seen
G everywhere we can reach from it)
5
Modification of depth first search Tracing modified DFS from A
Modified DFS starting from v: S = {}
all vertices coloured white
C S=A
create a stack S
colour v grey and push v onto S B B
while S is non-empty A S=A
peek at the top u of S E C
D
if u has a grey neighbour, there is a B
cycle S=A
else if u has a white neighbour w,
colour w grey and push it onto S B
pop: S=A
else colour u black and pop S
Tracing modified DFS from A
Pseudocode for BFS and DFS
(continued)
push: D • To compute complexity, I will be referring to an adjacency
B list implementation
C
S=A • Assume that we have a method which returns the first
B unmarked vertex adjacent to a given one:
A E GraphNode firstUnmarkedAdj(GraphNode v)
E D list of v’s neighbours
D B
S=A
E has a grey neighbour: B!
Found a loop! v u1(marked) u2(unmarked) u3(unmarked)
bookmark
Implementation of Pseudocode for breadth-first
firstUnmarkedAdj() search starting from vertex s
• We keep a pointer into the adjacency list of each vertex so s.marked = true; // marked is a field in
that we do not start to traverse the list of adjacent vertices // GraphNode
from the beginning each time. Queue Q = new Queue();
• Or we use the same iterator for this list, so when we call Q.enqueue(s);
next() it returns the next element in the list – again does while(! Q.isempty()) {
not start from the beginning. v = Q.dequeue();
u = firstUnmarkedAdj(v);
v u1(marked) u2(unmarked) u3(unmarked) while (u != null){
u.marked = true;
currUnmarkedAdj
Q.enqueue(u);
u = firstUnmarkedAdj(v);}}}
6
Space Complexity of BFS and
Pseudocode for DFS DFS
s.marked = true; • Need a queue/stack of size |V| (the number of vertices).
Stack S = new Stack(); Space complexity O(V).
S.push(s);
while(! S.isempty()){
v = S.peek();
u = firstUnmarkedAdj(v);
if (u == null) S.pop();
else {
u.marked = true;
S.push(u);
}
}
Time Complexity of BFS and
DFS Time complexity of BFS
• In terms of the number of vertices V: two nested loops Adjacency lists:
over V, hence O(V2). V E
v1
• More useful complexity estimate is in terms of the number v0: {v1,v2}
of edges. Usually, the number of edges is less than V2. v1: {v3}
v0 v3
v2: {v3}
v3: {}
v2
Time complexity of BFS Time complexity of BFS
Adjacency lists: Adjacency lists:
V E V E
v1 v1
v0: {v1,v2} mark, enqueue v0: {v1,v2} dequeue v0;
v0 mark, enqueue v1,v2
v0 v3 v0 v3
v1: {v3} v1: {v3}
v2: {v3} v2: {v3}
v3: {} v3: {}
v2 v2
7
Time complexity of BFS Time complexity of BFS
Adjacency lists: Adjacency lists:
V E V E
v1 v1
v0: {v1,v2} v0: {v1,v2}
v1: {v3} dequeue v1; mark, v1: {v3}
v0 v3 v0 v3
enqueue v3 v2: {v3} dequeue v2, check
v2: {v3} its adjacency list (v3
v3: {} already marked)
v2 v2 v3: {}
Time complexity of BFS Time complexity of BFS
Adjacency lists: Adjacency lists:
V E V E
v1 v1
v0: {v1,v2} v0: {v1,v2} |E0| = 2
v1: {v3} v1: {v3} |E1| = 1
v0 v3 v0 v3
v2: {v3} v2: {v3} |E2| = 1
v3: {} dequeue v3; check its v3: {} |E3| = 0
adjacency list Total number of steps:
v2 v2 |V| + |E0| + |E1| + |E2| +|E3|
=
= |V|+|E|.
Complexity of breadth-first
search Complexity of depth-first search
• Assume an adjacency list representation, V is the number • Each vertex is pushed on the stack and popped at most
of vertices, E the number of edges. once.
• Each vertex is enqueued and dequeued at most once.
• For every vertex we check what the next unvisited
• Scanning for all adjacent vertices takes O(|E|) time, since neighbour is.
sum of lengths of adjacency lists is |E|.
• Gives a O(|V|+|E|) time complexity. • In our implementation, we traverse the adjacency list only
once. This gives O(|V|+|E|) again.