Data Structures
Lecture 07: Graphs
Edirlei Soares de Lima
Graphs
Edge
G = (V, E)
G: graph;
V: set of vertices;
E: set of edges;
Vertex
Graphs
Directed graph: a graph in which edges have orientations.
An edge (u, v) leaves vertex u and enters vertex v.
Vertex v is adjacent to vertex u.
Edges from a vertex to itself are allowed (self-loops).
Graphs
Undirected graph: a graph in which edges have orientations.
Edges (u, v) and (v, u) are considered as a single edge. The adjacency
relationship is symmetric.
Self-loops are not allowed.
Graphs
Weighted graph: directed on undirected graph in which a
number (the weight) is assigned to each edge.
Graphs
Degree of a vertex:
In undirected graphs: is the number of edges that are incident to the
vertex.
In directed graphs: is the number of edges that leave the vertex (out-
degree) plus the number of edges that enter the vertex (in-dregree).
A vertex of degree zero is called isolated or unconnected.
Graphs
Path between vertices:
A path of length k from a vertex x to a vertex y in a graph G = (V, A) is a
sequence of vertices (v0, v1, v2, ... , vk) such that x = v0 and y = vk, and vi ∈
V for i = 1, 2, ... , k.
The length of a path is the number of edges on it, that is, the path
contains the vertices v0, v1, v2, ... , vk and the edges (v0, v1), (v1, v2), ... ,
(
vk-1, vk).
If there is a path c from x to y then y is reachable from x via c.
Graphs
Cycles:
In a directed graph: a path (v0, v1, ... , vk) forms a cycle if v0 = vk and the
path contains at least one edge.
The self-loop is a size 1 loop.
In an undirected graph: a path (v0, v1, ... , vk) forms a cycle if v0 = vk and
the path contains at least three edges.
Graphs
Connected components:
An undirected graph is connected if each pair of vertices is connected by
a path.
Connected components are the connected parts of a graph.
An undirected graph is connected if it has exactly one connected
component.
Graphs
Strongly connected components:
A directed graph G = (V, A) is strongly connected if any two vertices are
reachable from each other.
The strongly connected components of a directed graph are sets of
vertices under the relationship “are mutually reachable”.
A strongly connected directed graph has only one strongly connected
component.
Graph Representation
each vertex v V.
For each u V, Adj[u] contains references to all vertices v such
that (u, v) A. That is, Adj[u] contains all adjacency vertices of u.
Graph Representation
Given a graph G = (V, E), we assume that vertices are labeled with
numbers 1, 2, . . . , |V|.
The adjacency matrix is a matrix Aij of dimensions |V| × |V|,
where:
1
0
ꢃꢄ ꢃ, ꢀ
ꢆꢇꢈꢉꢊꢋꢃꢌꢉ
ꢂ
= ቊ
Graph Vertex Generic Class
public class GraphVertex<T>
{
private T data;
public GraphVertex(T t){
data = t;
}
edges.InsertAtEnd(v);
}
get { return edges; }
set { edges = value; }
}
public T Data{
get { return data; }
set { data = value; }
}
}
Graph Generic Class
public enum GraphType { Directed, Undirected };
public class Graph<T>
{
public GraphType graphType;
public Graph(GraphType t){
graphType = t;
}
...
Graph
Basic operations:
CreateVertex: creates a vertex and adds it to the list of vertices of the
graph.
AddEdge: adds an edge (u, v) to the graph (connecting vertex u to
vertex v).
Traversal/Search: traverses or search for the path to a certain element
in the graph. Several traversal strategies can be used:
Depth-First Search (DFS);
Dijkstra algorithm;
A* algorithm;
Graph CreateVertex
Creates a vertex and adds it to the list of vertices of the
graph.
public GraphVertex<T> CreateVertex(T t)
{
GraphVertex<T> vertex = new GraphVertex<T>(t);
vertices.InsertAtEnd(vertex);
return vertex;
}
Adds an edge (u, v) to the graph (connecting vertex u to
vertex v).
If the graph is undirected, also connects vertex v to vertex u.
public void AddEdge(GraphVertex<T> v, GraphVertex<T> u)
{
if (graphType == GraphType.Directed)
{
}
if (graphType == GraphType.Undirected)
{
}
}
Graph Creating a Directed Graph
Example:
Graph<string> directedGraph = new Graph<string>(GraphType.Directed);
GraphVertex<string> u = directedGraph.CreateVertex("u");
GraphVertex<string> v = directedGraph.CreateVertex("v");
GraphVertex<string> w = directedGraph.CreateVertex("w");
GraphVertex<string> x = directedGraph.CreateVertex("x");
GraphVertex<string> y = directedGraph.CreateVertex("y");
GraphVertex<string> z = directedGraph.CreateVertex("z");
Graph Creating a Undirected Graph
Example:
Graph<int> undirectedGraph = new Graph<int>(GraphType.Undirected);
GraphVertex<int> v1 = undirectedGraph.CreateVertex(1);
GraphVertex<int> v2 = undirectedGraph.CreateVertex(2);
GraphVertex<int> v3 = undirectedGraph.CreateVertex(3);
GraphVertex<int> v4 = undirectedGraph.CreateVertex(4);
GraphVertex<int> v5 = undirectedGraph.CreateVertex(5);
GraphVertex<int> v6 = undirectedGraph.CreateVertex(6);
BFS is one of the simplest algorithms for exploring a graph.
Given a graph G = (V, E) and a vertex s, called the source, the algorithm
systematically explores the edges of G in order to visit all vertices that
are reachable from s.
The algorithm expands the boundary between discovered and
undiscovered vertices evenly across the width of the
boundary.
The algorithm discovers all vertices at a distance k from the origin
vertex before discovering any vertices at a distance k + 1.
The graph can be directed or undirected.
Strategy:
The algorithm discovers all vertices at a distance k from the origin
vertex before discovering any vertices at a distance k + 1.
A
B
C
D
E
F
G
Algorithm:
To control the search, the BFS algorithm paints each vertex
white, gray or black.
All vertices start white and can later become gray and then
black.
White: not visited;
Gray: visited;
Black: completely explored (all adjacent nodes were visited).
BFS(G, s)
for each u V[G]
c[u] ← white
d[u] ← ∞
[u] ← NULL
c[s] ← gray
d[s] ← 0
Q ← {s} //Queue Structure
while Q ∅
for each v E[u]
if c[v] = white
c[v] ← gray
d[v] ← d[u] + 1
[v] ← u
enqueue(Q,v)
dequeue(Q)
c[u] ← black
for each u V[G]
c[u] ← white
d[u] ← ∞
[u] ← NULL
c[s] ← gray
d[s] ← 0
Q ← {s} //Queue
for each v E[u]
if c[v] = white
c[v] ← gray
d[v] ← d[u] + 1
[v] ← u
enqueue(Q,v)
dequeue(Q)
c[u] ← black
for each v E[u]
if c[v] = white
c[v] ← gray
d[v] ← d[u] + 1
[v] ← u
enqueue(Q,v)
dequeue(Q)
c[u] ← black
for each v E[u]
if c[v] = white
c[v] ← gray
d[v] ← d[u] + 1
[v] ← u
enqueue(Q,v)
dequeue(Q)
c[u] ← black
for each v E[u]
if c[v] = white
c[v] ← gray
d[v] ← d[u] + 1
[v] ← u
enqueue(Q,v)
dequeue(Q)
c[u] ← black
BFS(G, s)
for each u V[G]
c[u] ← white
d[u] ← ∞
[u] ← NULL
c[s] ← gray
d[s] ← 0
Q ← {s} //Queue Structure
while Q ∅
Each vertex of V is placed on queue Q at
most once: O(V);
The adjacency list of any vertex of u is
traversed only when the vertex is
removed from the queue;
The sum of all adjacent lists is O(E), so
the total time spent on the adjacent lists
is O(E);
for each v E[u]
if c[v] = white
c[v] ← gray
d[v] ← d[u] + 1
[v] ← u
Queuing and dequeuing cost O(1);
BFS complexity: O(V + E)
enqueue(Q,v)
dequeue(Q)
c[u] ← black
public Dictionary<GraphVertex<T>, GraphVertex<T>> BFS(GraphVertex<T> s)
{
Dictionary<GraphVertex<T>, int> d = new Dictionary<GraphVertex<T>, int>();
Dictionary<GraphVertex<T>, int> c = new Dictionary<GraphVertex<T>, int>();
Dictionary<GraphVertex<T>, GraphVertex<T>> pi = new
Dictionary<GraphVertex<T>, GraphVertex<T>>();
Queue<GraphVertex<T>> Q = new Queue<GraphVertex<T>>();
foreach (GraphVertex<T> u in vertices)
{
c[u] = 0; //white
d[u] = int.MaxValue;
pi[u] = null;
}
c[s] = 1; //gray
d[s] = 0;
Q.Enqueue(s);
while (!Q.IsEmpty())
{
QueueNode<GraphVertex<T>> u = Q.Dequeue();
...
...
foreach (GraphVertex<T> v in u.Data.Edges)
{
if (c[v] == 0) //white
{
c[v] = 1;
d[v] = d[u.Data] + 1;
pi[v] = u.Data;
Q.Enqueue(v);
}
}
c[u.Data] = 2; //black
}
return pi;
}
It is possible to reconstruct the search tree with the information
of π i:
It is possible to reconstruct the search tree with the information
of π i:
Assignment 1 Graphs
1
) Create a function to use the information stored in π to print
the path from the start vertex to another vertex of the graph.
The path must be displayed in the correct order.
Hint: when reading the information from π, the path will be inverted,
but you can use a stack structure to store the vertices from π and
easily retrieve them in the correct order.
Depth-First Search (DFS)
The DFS strategy is to seek the deepest vertex in the graph
whenever possible:
Edges are explored from the most recently discovered vertex v that
still has unexplored edges.
When all edges of v have been explored, the search moves
backwards to explore the edges of the vertex from which v
was discovered (backtracking).
Depth-First Search (DFS)
Strategy:
Edges are explored from the most recently discovered vertex v that
still has unexplored edges.
A
B
D
C
E
F
M
N
P
Q
Depth-First Search (DFS)
Algorithm:
To control the search, the DFS algorithm paints each vertex
white, gray or black.
All vertices start white and can later become gray and then
black.
White: not visited;
Gray: visited;
Black: completely explored (all adjacent nodes were visited).
Depth-First Search (DFS)
Algorithm:
The DFS algorithm marks each vertex with a timestamp.
Each vertex has two timestamps:
d[v]: indicates the time when v was visited (painted with grey);
f[v]: indicates the time when v was completely explored (all
adjacent nodes were visited) (painted black).
Depth-First Search (DFS)
DFS(G)
for each u V[G]
c[u] ← white
[u] ← NULL
time ← 0
for each u V[G]
if c[u] = white
Visit(u)
Visit(u)
c[u] gray
d[u] time ← time + 1
for each v E[u]
if c[v] = white
[v] u
Visit(v)
c[u] black
f[u] time ← time + 1
Depth-First Search (DFS)
for each u V[G]
c[u] ← white
[u] ← NULL
time ← 0
for each u V[G]
if c[u] = white
Visit(u)
u
w
/
v
w
/
y
w
/
x
w
/
w
w
/
z
w
/
c
d
f
Depth-First Search (DFS)
c[u] gray
d[u] time ← time + 1
for each v E[u]
if c[v] = white
[v] u
Visit(v)
c[u] black
f[u] time ← time + 1
u
g
/
v
w
/
y
w
/
x
w
/
w
w
/
z
w
/
c
d
f
1
Depth-First Search (DFS)
c[u] gray
d[u] time ← time + 1
for each v E[u]
if c[v] = white
[v] u
Visit(v)
c[u] black
f[u] time ← time + 1
u
g
/
v
g
u
2
y
w
/
x
w
/
w
w
/
z
w
/
c
d
f
1
Depth-First Search (DFS)
c[u] gray
d[u] time ← time + 1
for each v E[u]
if c[v] = white
[v] u
Visit(v)
c[u] black
f[u] time ← time + 1
u
g
/
v
g
u
2
y
g
v
3
x
w
/
w
w
/
z
w
/
c
d
f
1
Depth-First Search (DFS)
c[u] gray
d[u] time ← time + 1
for each v E[u]
if c[v] = white
[v] u
Visit(v)
c[u] black
f[u] time ← time + 1
u
g
/
v
g
u
2
y
g
v
3
x
g
y
4
w
w
/
z
w
/
c
d
f
1
Depth-First Search (DFS)
c[u] gray
d[u] time ← time + 1
for each v E[u]
if c[v] = white
[v] u
Visit(v)
c[u] black
f[u] time ← time + 1
u
g
/
v
g
u
2
y
g
v
3
x
b
y
4
5
w
w
/
z
w
/
c
d
f
1
Depth-First Search (DFS)
c[u] gray
d[u] time ← time + 1
for each v E[u]
if c[v] = white
[v] u
Visit(v)
c[u] black
f[u] time ← time + 1
u
g
/
v
g
u
2
y
b
v
3
6
x
b
y
4
5
w
w
/
z
w
/
c
d
f
1
Depth-First Search (DFS)
c[u] gray
d[u] time ← time + 1
for each v E[u]
if c[v] = white
[v] u
Visit(v)
c[u] black
f[u] time ← time + 1
u
g
/
v
b
u
2
7
y
b
v
3
6
x
b
y
4
5
w
w
/
z
w
/
c
d
f
1
Depth-First Search (DFS)
c[u] gray
d[u] time ← time + 1
for each v E[u]
if c[v] = white
[v] u
Visit(v)
c[u] black
f[u] time ← time + 1
u
b
/
v
b
u
2
7
y
b
v
3
6
x
b
y
4
5
w
w
/
z
w
/
c
d
f
1
8
Depth-First Search (DFS)
for each u V[G]
c[u] ← white
[u] ← NULL
time ← 0
for each u V[G]
if c[u] = white
Visit(u)
u
b
/
v
b
u
2
7
y
b
v
3
6
x
b
y
4
5
w
w
/
z
w
/
c
d
f
1
8
Depth-First Search (DFS)
c[u] gray
d[u] time ← time + 1
for each v E[u]
if c[v] = white
[v] u
Visit(v)
c[u] black
f[u] time ← time + 1
u
b
/
v
b
u
2
7
y
b
v
3
6
x
b
y
4
5
w
g
/
z
w
/
c
d
f
1
8
9
Depth-First Search (DFS)
c[u] gray
d[u] time ← time + 1
for each v E[u]
if c[v] = white
[v] u
Visit(v)
c[u] black
f[u] time ← time + 1
u
b
/
v
b
u
2
7
y
b
v
3
6
x
b
y
4
5
w
g
/
z
g
c
d
f
w
10
1
8
9
Depth-First Search (DFS)
c[u] gray
d[u] time ← time + 1
for each v E[u]
if c[v] = white
[v] u
Visit(v)
c[u] black
f[u] time ← time + 1
u
b
/
v
b
u
2
7
y
b
v
3
6
x
b
y
4
5
w
b
z
b
c
d
f
/
w
10
11
1
8
9
12
Depth-First Search (DFS) Analysis
DFS(G)
for each u V[G]
c[u] ← white
[u] ← NULL
The Visit function is called exactly once
for each vertex u V (visit is called only
for white vertices and the first action is
to paint the vertex gray). Therefore:
O(V);
time ← 0
for each u V[G]
if c[u] = white
Visit(u)
The main loop of Visit function is O(E);
Visit(u)
c[u] gray
DFS complexity: O(V + E)
d[u] time ← time + 1
for each v E[u]
if c[v] = white
[v] u
Visit(v)
c[u] black
f[u] time ← time + 1
Depth-First Search (DFS) C#
public Dictionary<GraphVertex<T>, GraphVertex<T>> DFS(GraphVertex<T> s)
{
Dictionary<GraphVertex<T>, int> d = new Dictionary<GraphVertex<T>, int>();
Dictionary<GraphVertex<T>, int> c = new Dictionary<GraphVertex<T>, int>();
Dictionary<GraphVertex<T>, int> f = new Dictionary<GraphVertex<T>, int>();
Dictionary<GraphVertex<T>, GraphVertex<T>> pi = new
Dictionary<GraphVertex<T>, GraphVertex<T>>();
int time = 1;
foreach (GraphVertex<T> u in vertices)
{
c[u] = 0; //white
d[u] = int.MaxValue;
f[u] = int.MaxValue;
pi[u] = null;
}
foreach (GraphVertex<T> u in vertices)
{
if (c[u] == 0){ //white
DFSVisit(u, ref time, d, c, f, pi);
}
}
return pi;
}
Depth-First Search (DFS) C#
private void DFSVisit(GraphVertex<T> u, ref int time,
Dictionary<GraphVertex<T>, int> d,
Dictionary<GraphVertex<T>, int> c,
Dictionary<GraphVertex<T>, int> f,
Dictionary<GraphVertex<T>, GraphVertex<T>> pi)
{
c[u] = 1; //gray
d[u] = time++;
foreach (GraphVertex<T> v in u.Edges)
{
if (c[v] == 0) //white
{
pi[v] = u;
DFSVisit(v, ref time, d, c, f, pi);
}
}
c[u] = 2; //black
f[u] = time++;
}
Applications in Game Programming
Pathfinding: while simple movements can be manually
defined (patrol routes or wander regions), more complex
movements must be computed during the game. Pathfinding
algorithms rely on graph structures to represent the level
structure.
Finite State Machines: are used control the behavior of
characters and are modeled as graph structures. Actions are
associated with state (vertices) and transitions (edges) have
set of associated conditions.
Problem-solving: puzzle games can generally be solved by
searching in the state space for a set of actions that lead from
the current state to a goal state.
Assignment 2 Graphs
1
) In the class of Game Frameworks, you have learned how to
implement pathfinding in a tilemap using an existing A*
function. Based on the structure of that example, adapt the
BFS and DFS functions to use them to perform the pathfinding
in a tilemap.
Important: the current versions of the BFS and DFS functions are
exploring the entire graph structure. But for pathfinding, the process
must stop when the target vertex is found.
The tilemap matrix can be interpreted as a graph structure: