# Graph Implementations Reading

## Table of contents

So far, we’ve been introduced to one particular type of graph: **simple**, **undirected**, and **unweighted**.

## What problems arise when using BFS for navigation directions in HuskyMaps if vertices are intersections and edges are roads?

Breadth-first search solves s–t shortest paths in an unweighted graph. This is an appropriate model only if the length of each edge (modeling either distance or travel time) is roughly the same. In reality, roads come in many different varieties: some are longer than others, some allow for higher or lower speed limits, some get congested during certains times of the day, and some roads are one-way only.

In this lesson, we’ll explore some fundamental graph design choices that allow us to better model a wider variety of problems.

- Weighted graphs
- Edges are weighted (assigned a number or cost).
- Directed graphs (digraphs)
- Edges are directed (one-way).

## Graph APIs

Application programming interfaces (APIs) define the contract between clients and implementers. In Java programs, APIs define available method signatures and their expected behaviors for all implementing classes.

Graphs represent a class of abstract data types. The exact API for a graph depends on the particular problem and the solution devised by the implementer. Nonetheless, there are several common design patterns employed by graph implementers. Consider the following (simplified) graph API for HuskyMaps. `StreetMapGraph`

is a weighted graph with `Long`

vertices and `WeightedEdge<Long>`

edges.

```
class StreetMapGraph {
// Each vertex refers to a specific, real-world Location.
private Map<Long, Location> nodes = new HashMap<>();
// Each vertex has weighted edges to its neighbors.
private Map<Long, Set<WeightedEdge<Long>>> neighbors = new HashMap<>();
// Initialize the graph from a real dataset.
StreetMapGraph(String filename) { ... }
// Nearest neighbor search and autocomplete.
long closest(Location target) { ... }
List<String> getLocationsByPrefix(String prefix) { ... }
List<Location> getLocations(String locationName) { ... }
// Getter methods for graph algorithms.
List<WeightedEdge<Long>> neighbors(long v) { ... }
Set<Long> vertices() { ... }
Location location(long id) { ... }
}
class WeightedEdge<Vertex> {
private Vertex v
private Vertex w;
private double weight;
...
}
```

Note that this graph API does not contain any graph algorithms. Instead, graph algorithms are implemented in separate classes and query the graph through the public methods. As a consequence, the methods we provide can have a significant impact on our graph algorithm implementations. For example, we might implement breadth-first search on a `StreetMapGraph`

as follows.

```
class BreadthFirstPaths {
private Set<Long> marked = new HashSet<>();
private Map<Long, Long> edgeTo = new HashMap<>();
private Map<Long, Double> distTo = new HashMap<>();
// Run BFS on the StreetMapGraph g from vertex s.
BreadthFirstPaths(StreetMapGraph g, long s) {
Queue<Long> fringe = new LinkedList<>();
fringe.add(s);
marked.add(s);
edgeTo.put(s, s);
distTo.put(s, 0);
while (!fringe.isEmpty()) {
long v = fringe.remove();
for (WeightedEdge<Long> edge : g.neighbors(v)) {
assert v == edge.from();
long w = edge.to();
if (!marked.contains(w)) {
marked.add(w);
edgeTo.put(w, v);
distTo.put(w, distTo.get(v) + 1);
fringe.add(w);
}
}
}
}
// Returns true if there is a path to t.
boolean hasPathTo(long t) {
return marked.contains(t);
}
// Get the s-t shortest path.
List<Long> pathTo(long t) { ... }
// Get the number of edges on the s-t shortest path.
double distTo(long t) {
return distTo.getOrDefault(t, Double.POSITIVE_INFINITY);
}
}
```

This idea of separating data structure `StreetMapGraph`

from the processing algorithm `BreadthFirstPaths`

is an example of a typical **graph solver design pattern**. After the HuskyMaps server starts up and creates the global `StreetMapGraph`

instance, we can use `BreadthFirstPaths`

to answer a query.

- Create a new
`BreadthFirstPaths`

instance and pass the`StreetMapGraph`

to the constructor. - The constructor for
`BreadthFirstPaths`

runs BFS from the given vertex and stores the shortest paths in its internal data structures. - Query
`BreadthFirstPaths`

using`hasPathTo`

,`pathTo`

, and`distTo`

for navigation directions.

## Graph data structures

Just as there are several data structures for sets, maps, and priority queues, so too are there different data structures for graphs. To evaluate the runtime of these data structures, consider the following method that prints out all of the edges in a graph with `int`

IDs.

```
void printEdges(Graph g) {
for (int v : g.vertices()) {
for (int w : g.neighbors(v)) {
System.out.println(v + "-" + w);
}
}
}
```

### Adjacency matrix

An **adjacency matrix** maintains a matrix indexed by vertex ID, i.e. `boolean[][]`

. There is an edge between `v`

and `w`

if and only if the corresponding cell contains `true`

. In undirected graphs, the adjacency matrix is symmetric along its diagonal.

## If there are V vertices in a simple graph, up to how many edges can be in the graph?

Up to V^{2} - V edges since all the values along the main diagonal must be false.

### Adjacency list

An **adjacency list** maintains an array of lists indexed by vertex ID. Adjacency lists are the most popular approach for representing graphs because most problems are sparse.

- Sparse graph
- E grows slowly, i.e. proportional to V.
- Dense graph
- E grows quickly, i.e. proportional to V
^{2}.

## Give a tight asymptotic runtime bound for BreadthFirstPaths in terms of V and E assuming an adjacency list representation.

O(V + E). In a directed graph, each vertex `v`

is visited at most once and each `edge`

is considered at most once.

Graph traversal algorithms are affected by the time it takes to evaluate `g.neighbors(v)`

. In an adjacency list, only the **incident edges** (edges to true neighbors) need to be considered. In an adjacency matrix on the other hand, it’s necessary to iterate over all V - 1 vertices since we don’t know which edges are incident to a given vertex.

The runtime for DFS follows from our analysis of BFS.

Graph Problem | Algorithm | Adj. matrix | Adj. list |
---|---|---|---|

s–t paths | Depth-first search | O(V^{2}) | O(V + E) |

s–t shortest paths | Breadth-first search | O(V^{2}) | O(V + E) |

## Does StreetMapGraph use adjacency matrix or adjacency list representation?

`StreetMapGraph`

uses an adjacency list representation. Each `Long`

vertex is mapped to a list of neighbors, implemented as a `Set<WeightedEdge<Long>>`

.