Link
Graph Implementations
Graph API as a design problem, two graph representations, and the implications of these representations on algorithms.
Kevin Lin, with thanks to many others.
1
Ask questions anonymously on Piazza. Look for the pinned Lecture Questions thread.

Feedback from the Reading Quiz
2

Using vs. Designing Graphs
Client uses graph algorithms like BFS and DFS to solve real-world problems.

ADT specifies the API for interacting with graphs, like how to get the neighbors of v.

Implementer designs a performant data structure representation for the ADT.
3
Implementer
Client
ADT
Graph API is a design problem!
API stands for Application Programming Interface. This is how we realize an ADT in code either as a Java interface or a class (which implies some data structure implementation).

Convention: Integer Vertices
Refer to vertices using a unique integer ID rather than a properly-named label.


Use maps or other custom data types to convert between ID and real vertex data.
4
Austin
Dallas
Houston
0
1
2
Map<String, Integer>
  Austin: 0
  Dallas: 1
  Houston: 2

5
Example Graph API
Create an empty graph for V vertices.
Add an edge (v, w).
Return the neighboring vertices of v.
Return the total number of vertices, V.
Return the total number of edges, V.
class Graph
  Graph(int V)
  void addEdge(int v, int w)
  Iterable<Integer> adj(int v)
  int V()
  int E()


6
Design Tradeoffs
Number of vertices (V) must be specified in the graph constructor.

Number of neighbors for a vertex v: get adj and then return the size of the list.

Unweighted graph only!
class Graph
  Graph(int V)
  void addEdge(int v, int w)
  Iterable<Integer> adj(int v)
  int V()
  int E()


7
Using the Graph API
Write a client method to print a graph.
class Graph
  Graph(int V)
  void addEdge(int v, int w)
  Iterable<Integer> adj(int v)
  int V()
  int E()

Q
1
3
2
4
$ java printDemo
1 - 2
1 - 4
2 - 1
2 - 3
3 - 2
4 - 1
void print(Graph G) {





}
Q1: Write a client method to print a graph.

8
Using the Graph API
Write a client method to print a graph.
class Graph
  Graph(int V)
  void addEdge(int v, int w)
  Iterable<Integer> adj(int v)
  int V()
  int E()

A
1
3
2
4
$ java printDemo
1 - 2
1 - 4
2 - 1
2 - 3
3 - 2
4 - 1
void print(Graph G) {
  for (int v = 0; v < G.V(); v++) {
    for (int w : G.adj(v)) {
      println(v + "-" + w);
    }
  }
}

9
HuskyMaps Graph API
class StreetMapGraph
  StreetMapGraph(String filename)
  long closest(double lat, double lon)
  List<String> getLocationsByPrefix(String prefix)
  List<WeightedEdge<Long>> neighbors(long v)
  Set<Long> vertices()
  void addWeightedEdge(WeightedEdge<Long> edge)  ...
All of these methods work on static data: that is, they’re data structure queries. For closest, we use a k-d tree. For getLocationsByPrefix, we use a sorted array. For neighbors, vertices, and adding a weighted edge, we use a table-like data structure to represent out underlying graph. They are separate from graph algorithms like DFS and BFS.

Implementer
Graph Representations
10
There are different ways to implement trees, so there are also different ways to implement graphs.

Tree Representations: Nodes vs. Arrays
Store items together with structure.
Map parent to child relationships.
Store items separate from structure.
Map child to parent relationships.
No explicit links needed!
11
B
A
C
A
B
C
2
B
C
A
items
0
0
0
parents
1
1
2
0
0

Graph Representation 1: Adjacency Matrix
Directed graph: a[s][t].
Undirected graph: a[v][w], a[w][v].
12
0
1
2
0
0
1
1
1
0
0
1
2
0
0
0
s
t
0
1
2
3
0
0
1
0
0
1
1
0
1
0
2
0
1
0
1
3
0
0
1
0
v
w
0
1
2
0
1
3
2

Adjacency Matrix Runtime
G.adj(2) returns an iterable of [1, 3].
Runtime to iterate over all neighbors of v is Θ(V): adj needs to return a new iterable containing all the indices with value 1.
13
0
1
2
3
0
0
1
0
0
1
1
0
1
0
2
0
1
0
1
3
0
0
1
0
v
w
0
1
3
2

Adjacency Matrix Runtime
Give the order of growth of the runtime for print if the graph is an adjacency matrix, where V is the number of vertices and E is the number of edges.
14
0
1
2
3
0
0
1
0
0
1
1
0
1
0
2
0
1
0
1
3
0
0
1
0
v
w
0
1
3
2
void print(Graph G) {
  for (int v = 0; v < G.V(); v++) {
    for (int w : G.adj(v)) {
      println(v + "-" + w);
    }
  }
}
Q
Q1: Give the order of growth of the runtime for print if the graph is an adjacency matrix, where V is the number of vertices and E is the number of edges.

Give the order of growth of the runtime for print if the graph is an adjacency matrix, where V is the number of vertices and E is the number of edges.
15

Adjacency Matrix Runtime
Give the order of growth of the runtime for print if the graph is an adjacency matrix, where V is the number of vertices and E is the number of edges.
16
0
1
2
3
0
0
1
0
0
1
1
0
1
0
2
0
1
0
1
3
0
0
1
0
v
w
0
1
3
2
void print(Graph G) {
  for (int v = 0; v < G.V(); v++) {
    for (int w : G.adj(v)) {
      println(v + "-" + w);
    }
  }
}
A
V iterations
Θ(V)

Edges vs. Vertices in a Simple Graph
Simple Graph. A graph with no self-loops and no parallel edges.
17
0
1
2
3
0
0
1
0
0
1
1
0
1
0
2
0
1
0
1
3
0
0
1
0
v
w
0
1
3
2
Self-loop
Parallel

Graph Representation 2: Adjacency List
Maintain array of lists indexed by vertex number.
Most popular approach for representing graphs.
18
0
1
2
[1, 2]
[2]
0
1
2

Adjacency List Runtime
Give the order of growth of the runtime for print if the graph is an adjacency list, where V is the number of vertices and E is the number of edges.
19
0
1
2
[1, 2]
[2]
0
1
2
Q
void print(Graph G) {
  for (int v = 0; v < G.V(); v++) {
    for (int w : G.adj(v)) {
      println(v + "-" + w);
    }
  }
}
V iterations
???
Q1: Give the order of growth of the runtime for print if the graph is an adjacency list, where V is the number of vertices and E is the number of edges.

Adjacency List Runtime
Give the order of growth of the runtime for print if the graph is an adjacency list, where V is the number of vertices and E is the number of edges.
20
0
1
2
[1, 2]
[2]
0
1
2
Q
void print(Graph G) {
  for (int v = 0; v < G.V(); v++) {
    for (int w : G.adj(v)) {
      println(v + "-" + w);
    }
  }
}
V iterations
Ω(1), O(V)
Q1: Give the order of growth of the runtime for print if the graph is an adjacency list, where V is the number of vertices and E is the number of edges.

Give the order of growth of the runtime for print if the graph is an adjacency list, where V is the number of vertices and E is the number of edges.
21

Adjacency List Runtime
Runtime is Θ(V + E), where
V is the number of vertices,
E is the number of edges.
Runtime depends on the density (or sparsity) of the graph.
Sparse graph. E grows slowly, e.g. E ∈ Θ(log V), then runtime is in Θ(V).
Dense graph. E grows quickly, e.g. E ∈ Θ(V2), then runtime is in Θ(V2).
22

Client
Graph Algorithms
23

24
HuskyMaps Graph API
class StreetMapGraph
  StreetMapGraph(String filename)
  long closest(double lat, double lon)
  List<String> getLocationsByPrefix(String prefix)
  List<WeightedEdge<Long>> neighbors(long v)
  Set<Long> vertices()
  void addWeightedEdge(WeightedEdge<Long> edge)  ...
These methods access data immediately available in the graph. Running traversals or searches on graphs is typically done in a different way.

Design Pattern: Graph Solvers
Design pattern for graph clients: Decouple graph type from processing algorithm.
Create a graph instance and populate it with data.
Pass the graph instance to the constructor of the client class.
The client class runs the algorithm in its constructor and stores the solutions.
Query the client class for the stored solutions.
25
class DepthFirstPaths
  DepthFirstPaths(Graph G, int s)
  boolean hasPathTo(int v)
  Iterable<Integer> pathTo(int v)
1
2
3
4
5
6
7
8
0
s
Demo

26
Recursive DepthFirstPaths
Instance variables store algorithm data.
marked[v] is true iff v connected to s.edgeTo[v] is vertex visited to get to v.
DepthFirstPaths constructor computes the result of the algorithm with the dfs recursive method.

How would we implement pathTo(v) and hasPathTo(v)?
private boolean[] marked;
private int[] edgeTo;
private int s;
DepthFirstPaths(Graph G, int s) {
  ...
  dfs(G, s);
}
private void dfs(Graph G, int v) {
  marked[v] = true;
  for (int w : G.adj(v)) {
    if (!marked[w]) {
      edgeTo[w] = v;
      dfs(G, w);
    }
  }
}
Q
Q1: How would we implement pathTo(v) and hasPathTo(v)?

27
Recursive DepthFirstPaths
Instance variables store algorithm data.
marked[v] is true iff v connected to s.edgeTo[v] is vertex visited to get to v.
DepthFirstPaths constructor computes the result of the algorithm with the dfs recursive method.

How would we implement pathTo(v) and hasPathTo(v)?
private boolean[] marked;
private int[] edgeTo;
private int s;
Iterable<Integer> pathTo(int v) {
  if (!hasPathTo(v)) return null;
  List<Integer> path = ...;
  for (int x=v; x!=s; x=edgeTo[x]) {
    path.add(x);
  }
  path.add(s);
  Collections.reverse(path);
  return path;
}
boolean hasPathTo(int v) {
  return marked[v];
}
A
Q1: How would we implement pathTo(v) and hasPathTo(v)?

28
DepthFirstPaths Runtime
Give a tight big-O runtime bound for the DepthFirstPaths constructor. Assume the adjacency list graph representation.

O(V)
O(V + E)
O(V2)
O(V * E)
private boolean[] marked;
private int[] edgeTo;
private int s;
DepthFirstPaths(Graph G, int s) {
  ...
  dfs(G, s);
}
private void dfs(Graph G, int v) {
  marked[v] = true;
  for (int w : G.adj(v)) {
    if (!marked[w]) {
      edgeTo[w] = v;
      dfs(G, w);
    }
  }
}
Q
Q1: Give a tight big-O runtime bound for the DepthFirstPaths constructor.

Give a tight big-O runtime bound for the DepthFirstPaths constructor.
29

30
DepthFirstPaths Runtime
Give a tight big-O runtime bound for the DepthFirstPaths constructor. Assume the adjacency list graph representation.

O(V + E). Or O(E).
Imagine a connected, undirected graph.
Cost model?
Each vertex is visited at most once.Each edge is checked at most twice.
private boolean[] marked;
private int[] edgeTo;
private int s;
DepthFirstPaths(Graph G, int s) {
  ...
  dfs(G, s);
}
private void dfs(Graph G, int v) {
  marked[v] = true;
  for (int w : G.adj(v)) {
    if (!marked[w]) {
      edgeTo[w] = v;
      dfs(G, w);
    }
  }
}
A
Vertex visits
Edge check
Q1: Give a tight big-O runtime bound for the DepthFirstPaths constructor.

31
BreadthFirstPaths
Instance variables store algorithm data.
marked[v] is true iff v connected to s.edgeTo[v] is vertex visited to get to v.
BreadthFirstPaths constructor computes the result of the algorithm with the bfs iterative method.

Cost model given undirected graph?
Each vertex is visited at most once.Each edge is checked at most twice.
private boolean[] marked;
private int[] edgeTo;
private void bfs(Graph G, int s) {
  Queue<Integer> fringe = ...;
  fringe.add(s);
  marked[s] = true;
  while (!fringe.isEmpty()) {
    int v = fringe.remove();
    for (int w : G.adj(v)) {
      if (!marked[w]) {
        fringe.add(w);
        marked[w] = true;
        edgeTo[w] = v;
      }
    }
  }
}
Demo

Memory usage in addition to graph: O(V) to store the marked and edgeTo arrays.
How does the efficiency compare between adjacency list and adjacency matrix?
32
Graph Problems
Problem
Problem Description
Solution
Efficiency (adj. list)
s-t paths
Find a path from s to every reachable vertex.
Depth-first search
O(V + E) runtime
Θ(V) space
s-t shortest paths
Find a shortest path from s to every reachable vertex.
Breadth-first search
O(V + E) runtime
Θ(V) space

If we use an adjacency matrix, BFS and DFS become O(V2). Terrible for sparse graphs!
Thus, we’ll always use adjacency lists unless otherwise stated.
33
Graph Problems
Problem
Problem Description
Solution
Efficiency (matrix)
s-t paths
Find a path from s to every reachable vertex.
Depth-first search
O(V2) runtime
Θ(V) space
s-t shortest paths
Find a shortest path from s to every reachable vertex.
Breadth-first search
O(V2) runtime
Θ(V) space

Summary
The design of our graph APIs is a choice!
Client classes implement algorithms that use the graph API to solve problems.

Two common representations for graphs.
Adjacency matrix.
Adjacency list (preferred).

Efficiency of graph algorithms depends on the graph representation.
34
Implementer
Client
ADT
Graph API is a design problem!