Lecture 24: Graphs II: Graph Traversal Implementations
- BreadthFirstPaths
- Graph API
- Graph Representations and Graph Algorithm Runtimes
- Graph Traversal Runtimes
- Layers of Abstraction
Tree and Graph Traversals
Just as there are many tree traversals:
- Preorder: DBACFEG
- Inorder: ABCDEFG
- Postorder: ACBEGFD
- Level order: DBFACEG
graph traversals, given some source:
- DFS Preorder: 012543678 (dfs calls).
- DFS Postorder: 347685210 (dfs returns).
- BFS order: Act in order of distance from s.
- BFS stands for “breadth first search”.
- Analogous to “level order”. Search is wide, not deep.
0 1 24 53 68 7
- Analogous to “level order”. Search is wide, not deep.
Shortest Paths Challenge
Goal: Given the graph above, find the shortest path from s to all other vertices.
- Give a general algorithm.
- Hint: You’ll need to somehow visit vertices in BFS order.
- Hint #2: You’ll need to use some kind of data structure.
- Hint #3: Don’t use recursion.
BFS Answer
Breadth First Search.
- Initialize a queue with a starting vertex s and mark that vertex.
- A queue is a list that has two operations: enqueue (a.k.a. addLast) and dequeue (a.k.a. removeFirst).
- Let’s call this the queue our fringe.
- Repeat until queue is empty:
- Remove vertex v from the front of the queue.
- For each unmarked neighbor n of v:
- Mark n.
- Set edgeTo[n] = v (and/or distTo[n] = distTo[v] + 1).
- Add n to end of queue.
A queue is the opposite of a stack. Stack has push (addFirst) and pop (removeFirst)
Do “distTo[n] = distTo[v] + 1” if you want to track distance value
Graph API und Graph Representations
To Implement our graph algorithms like BreadthFirstPaths and DepthFirstPaths, we need:
- An API (Application Programming Interface) for graphs.
- For our purposes today, these are our Graph methods, including their signatures and behaviors.
- Defines how Graph client programmers must think.
- An underlying data structure to represent our graphs.
Our choices can have profound implications on:
- Runtime.
- Memory usage.
- Difficulty of implementing various graph algorithms.
Graph API Decision #1: Integer Vertices 给每个点标一个数字代表这个点
Graph API
public class Graph {
public Graph(int V): Create empty graph with v vertices
public void addEdge(int v, int w): add an edge v-w
Iterable<Integer> adj(int v): vertices adjacent to v
int V(): number of vertices
int E(): number of edges
Some features:
- Number of vertices must be specified in advance.
- Does not support weights (labels) on nodes or edges.
- Has no method for getting the number of edges for a vertex (i.e. its degree)
Example client:
/** degree of vertex v in graph G */
public static int degree(Graph G, int v) {
int degree = 0;
for (int w : G.adj(v)) {
degree += 1;
}
return degree; }
Print client:
public static void print(Graph G) {
for (int v = 0; v < G.V(); v += 1) {
for (int w : G.adj(v)) {
System.out.println(v + “-” + w);
}
}
}
Our choice of Graph API has deep implications on the implementation of DepthFirstPaths, BreadthFirstPaths, print, and other graph “clients”.
Graph Representation and Graph Algorithm Runtime
Graph Representations
Graph Representation 1: Adjacency Matrix.
- G.adj(2) would return an iterator where we can call next() up to two times
- next() returns 1
- next() returns 3
- Total runtime to iterate over all neighbors of v is Θ(V).
- Underlying code has to iterate through entire array to handle next() and hasNext() calls.
Representation 2: Edge Sets: Collection of all edges.
Representation 3: Adjacency lists.
Runtime of some basic operations for each representation:
Bare-Bones Undirected Graph Implementation
public class Graph {
private final int V; private List<Integer>[] adj;
public Graph(int V) {
this.V = V;
adj = (List<Integer>[]) new ArrayList[V];
for (int v = 0; v < V; v++) {
adj[v] = new ArrayList<Integer>();
}
}
public void addEdge(int v, int w) {
adj[v].add(w); adj[w].add(v);
}
public Iterable<Integer> adj(int v) {
return adj[v];
}
}
Graph Traversal Implementations and Runtime
Depth First Search Implementation
- Common design pattern in graph algorithms: Decouple type from processing algorithm.
- Create a graph object.
- Pass the graph to a graph-processing method (or constructor) in a client class.
- Query the client class for information.\
client class :
public class Paths {
public Paths(Graph G, int s): Find all paths from G
boolean hasPathTo(int v): is there a path from s to v?
Iterable<Integer> pathTo(int v): path from s to v (if any)
}
DepthFirstPaths Demo
Goal: Find a path from s to every other reachable vertex, visiting each vertex at most once. dfs(v) is as follows:
- Mark v.
- For each unmarked adjacent vertex w:
- set edgeTo[w] = v.
- dfs(w)
code
public class DepthFirstPaths {
private boolean[] marked;
private int[] edgeTo;
private int s;
public 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);
}
}
}
public Iterable<Integer> pathTo(int v) {
if (!hasPathTo(v)) return null;
List<Integer> path = new ArrayList<>();
for (int x = v; x != s; x = edgeTo[x]) {
path.add(x);
}
path.add(s);
Collections.reverse(path);
return path;
}
public boolean hasPathTo(int v) {
return marked[v];
}
...
}
marked[v] is true iff v connected to s
edgeTo[v] is previous vertex on path from s to v
java.util.Collections.reverse() method is a java.util.Collections class method. It reverses the order of elements in a list passed as an argument.
Runtime for DepthFirstPaths
Graph Problems
BreadthFirstPaths Implementation
Code:
public class BreadthFirstPaths {
private boolean[] marked;
private int[] edgeTo;
...
private void bfs(Graph G, int s) {
Queue<Integer> fringe =
new Queue<Integer>();
fringe.enqueue(s);
marked[s] = true;
while (!fringe.isEmpty()) {
int v = fringe.dequeue();
for (int w : G.adj(v)) {
if (!marked[w]) {
fringe.enqueue(w);
marked[w] = true;
edgeTo[w] = v;
}
}
}
}
Graph Problems
s-t paths
-
Find a path from s to every reachable vertex.
-
- Goal: Find a path from s to every other reachable vertex, visiting each vertex at most once. dfs(v) is as follows:
- Mark v.
- For each unmarked adjacent vertex w:
- set edgeTo[w] = v.
- dfs(w)
- Goal: Find a path from s to every other reachable vertex, visiting each vertex at most once. dfs(v) is as follows:
+Efficiency (adj. list): O(V+E) time ,Θ(V) space
s-t shortest paths
-
Find a shortest path from s to every reachable vertex.
-
Solution:
-
Efficiency (adj. list): O(V+E) time, Θ(V) space
Layers of Abstraction
Our choice of how to implement the Graph API has profound implications on runtime.(如何设计API严重影响runtime,即用什么方式记录该点的邻居节点,我们介绍了三种方式,矩阵,hashset,以及一个存储list的数组,最后一个效率最高,因为没有冗余,即只用记录真实的邻居,不用向矩阵一样每个节点都必须记录其与所有其他节点的关系)