Why study graph algorithms?
graph | vertex | edge |
---|---|---|
communication | telephone, computer | fiber optic cable |
circuit | gate, register, processor | wire |
mechanical | joint | rod, beam, spring |
financial | stock, currency | transactions |
transportation | intersection | street |
internet | class C network | connection |
game | board position | legal move |
social relationship | person | friendship |
neural network | neuron | synapse |
protein network | protein | protein-protein interaction |
molecule | atom | bond |
polygonal mesh | geometry | adjacency |
Graph is made up of vertices (nodes, points) connected with edges (links, lines, arcs)
Two vertices are connected if there is a path between them
vertex: black dot; edge: line; path: green lines (len=4); cycle: blue lines (len=5); 3 connected components; red vertex has degree 4
problem | description |
---|---|
s-t path | Is there a path between \(s\) and \(t\)? |
shortest s-t path | What is the shortest path between \(s\) and \(t\)? |
cycle | Is there a cycle in the graph? |
Euler cycle | Is there a cycle that uses each edge exactly once? |
Hamilton cycle | Is there a cycle that uses each vertex exactly once? |
connectivity | Is there a path between every pair of vertices? |
biconnectivity | Is there a vertex whose removal disconnects the graph? |
planarity | Can the graph be drawn in the plane with no crossing edges? |
graph isomorphism | Are two graphs isomorphic? |
Challenge: Which graph problems are easy? difficult? intractable?
Graph drawing provides intuition about the structure of the graph
Caveat: Intuition can be misleading
Vertex representation
Anomalies: Not all graph representations can handle these
// 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++; return degree; }
Maintain a two-dimensional \(V\)-by-\(V\) boolean array;
for each edge \(v\)-\(w\) in graph: adj[v][w] = adj[v][w] = true
.
|
0123456789012 0: .11..11...... 1: 1............ 2: 1............ 3: ....11....... 4: ...1.11...... 5: 1..11........ 6: 1...1........ 7: ........1.... 8: .......1..... 9: ..........111 10: .........1... 11: .........1..1 12: .........1.1. |
Two entries for each edge
Which is order of growth of running time of the following code fragment if the graph uses the adjacency-matrix representation, where \(V\) is the number of vertices and \(E\) is the number of edges?
// prints each edge exactly once for(int v = 0; v < G.V(); v++) for(int w : G.adj(v)) StdOut.println(v + "-" + w);
A. \(V\)
B. \(E+V\)
C. \(V^2\)
D. \(VE\)
E. I don't know
Maintain vertex-indexed array of lists
|
|
Which is order of growth of running time of the following code fragment if the graph uses the adjacency-lists representation, where \(V\) is the number of vertices and \(E\) is the number of edges?
// prints each edge exactly once for(int v = 0; v < G.V(); v++) for(int w : G.adj(v)) StdOut.println(v + "-" + w);
A. \(V\)
B. \(E+V\)
C. \(V^2\)
D. \(VE\)
E. I don't know
In practice, use adjacency-lists representation
representation |
space |
add edge |
edge between \(v\) and \(w\) |
iterate over verts adj to \(v\) |
---|---|---|---|---|
list of edges | \(E\) | \(1\) | \(E\) | \(E\) |
adjacency matrix | \(V^2\) | \(1^\dagger\) | \(1\) | \(V\) |
adjacency lists | \(E+V\) | \(1\) | \(\degree(v)\) | \(\degree(v)\) |
\(^\dagger\)disallows parallel edges
public class Graph { private final int V; private Bag<Integer>[] adj; // adj lists public Graph(int V) { // create empty graph with V vertices this.V = V; adj = (Bag<Integer>[]) new Bag[V]; for(int v = 0; v < V; v++) adj[v] = new Bag<Integer>(); } public void addEdge(int v, int w) { // add edge v-w (parallel edges and self-loops allowed) adj[v].add(w); adj[w].add(v); } // iterator for vertices adjacent to v public Iterable<Integer> adj(int v) { return adj[v]; } }
Maze graph:
Graph: vertex for every intersection, edge for every passage
Goal: Explore every intersection in the maze
Algorithm:
Goal: systematically traverse a graph
Idea: mimic maze exploration (function-call stack acts as ball of string)
DFS (to visit a vertex v) Mark vertex v Recursively visit all unmarked verts w adj to v
Typical applications:
Design challenge: how to implement?
To visit a vertex \(v\):
Design pattern: decouple graph data type from graph processing
Graph
objectGraph
to a graph-processing routine// print all verts connected to s Paths paths = new Paths(G, s); for(int v = 0; v < G.V(); v++) if(paths.hasPathTo(v)) StdOut.println(v);
To visit a vertex \(v\)
Data structures:
marked[]
to mark verticesedgeTo[]
to keep track of paths (edgeTo[w] == v
) means that edge \(v\)-\(w\) taken to discover vertex \(w\)public class DepthFirstPaths { private int s; // marked[v] = true if v connected to s private boolean[] marked; // edgeTo[v] = previous vertex on path from s to v private int[] edgeTo; public DepthFirstPaths(Graph G, int s) { // initialize data structures /* ... */ // find vertices connected to s dfs(G, s); } // recursive DFS does the work 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); } } } }
Proposition: DFS marks all vertices connected to \(s\) in time proportional to the sum of their degrees (plus time to initialize the marked[]
array).
Pf (correctness):
Pf (running time): Each vertex connected to \(s\) is visited once |
|
Proposition: After DFS, can check if vertex \(v\) is connected to \(s\) in constant time and can find \(v\)-\(s\) path (if one exists) in time proportional to its length.
Pf. edgeTo[]
is parent-link representation of a tree rooted at vert \(s\).
public boolean hasPathTo(int v) { return marked[v]; } public Iterable<Integer> pathTo(int v) { if(!hasPathTo(v)) return null; Stack<Integer> path = new Stack<Integer>(); for(int x = v; x != s; x = edgeTo[x]) path.push(x); path.push(s); return path; }
Proposition: After DFS, can check if vertex \(v\) is connected to \(s\) in constant time and can find \(v\)-\(s\) path (if one exists) in time proportional to its length.
Pf. edgeTo[]
is parent-link representation of a tree rooted at vert \(s\).
Problem: Implement flood fill (Photoshop magic wand)
Challenge: Implement DFS without recursion
One solution (see link)
Challenge: Implement DFS without recursion
Alternative solution (space proportional to \(E+V\), vertex can appear on stack more than once)
Tree traversal: Many ways to explore every vertex in binary tree
|
|
Graph search: Many ways to explore every vertex in a graph
dfs(G,v)
dfs(G,v)
s
Repeat until queue is empty:
Repeat until queue is empty:
BFS (from source vertex s) Put s onto a FIFO queue, and mark s as visited Repeat until the queue is empty: Remove the least recently added vertex v For each unmarked neighbor of v: Add neighbor to the queue Mark neighbor |
|
public class BreadthFirstPaths { private boolean[] marked; private int[] edgeTo; private int[] distTo; /* ... */ private void BFS(Graph G, int s) { Queue<Integer> q = new Queue<Integer>(); q.enqueue(s); // init FIFO queue of vertices to explore marked[s] = true; distTo[s] = 0; while(!q.isEmpty()) { int v = q.dequeue(); for(int w : G.adj(v)) { if(!marked[w]) { // found new vertex w via edge v-w q.enqueue(w); marked[w] = true; edgeTo[w] = v; distTo[w] = distTo[v] + 1; } } } } }
Q. In which order does BFS examine vertices?
A. Increasing distance (number of edges) from s. Queue always consists of \(\geq 0\) vertices of distance \(k\) from \(s\), followed by \(\geq 0\) vertices of distance \(k+1\).
Proposition: In any connected graph \(G\), BFS computes shortest paths from \(s\) to all other vertices in time proportional to \(E+V\).
Fewest number of hops in a communication network
|
|
Problem: Identify connected components
How difficult? A. Any programmer could do it B. Typical diligent algorithms student could do it C. Hire an expert D. Intractible E. No one knows |
|
Problem: Is a graph bipartite?
How difficult? A. Any programmer could do it B. Typical diligent algorithms student could do it C. Hire an expert D. Intractible E. No one knows |
|
Problem: Find a cycle in a graph (if one exists).
How difficult? A. Any programmer could do it B. Typical diligent algorithms student could do it C. Hire an expert D. Intractible E. No one knows |
|
Problem: Is there a (general) cycle that uses every edge exactly once?
How difficult? A. Any programmer could do it B. Typical diligent algorithms student could do it C. Hire an expert D. Intractible E. No one knows |
|
Problem: Is there a cycle that contains every vertex exactly once?
How difficult? A. Any programmer could do it B. Typical diligent algorithms student could do it C. Hire an expert D. Intractible E. No one knows |
|
Problem: Are two graphs identical except for vertex names?
How difficult? A. Any programmer could do it B. Typical diligent algorithms student could do it C. Hire an expert D. Intractible E. No one knows |
|
Problem: Can you draw a graph in the plane with no crossing edges?
How difficult? A. Any programmer could do it B. Typical diligent algorithms student could do it C. Hire an expert D. Intractible E. No one knows |
|
BFS and DFS enables efficient solution of many (but not all) graph problems
graph problem | BFS | DFS | time |
---|---|---|---|
s-t path | X | X | \(E+V\) |
shortest s-t path | X | \(E+V\) | |
cycle | X | X | \(E+V\) |
Euler cycle | X | \(E+V\) | |
Hamilton cycle | \(\pow(2,1.657V)\) | ||
bipartiteness (odd cycle) | X | X | \(E + V\) |
connected components | X | X | \(E + V\) |
biconnected components | X | \(E + V\) | |
planarity | X | \(E + V\) | |
graph isomorphism | \(\pow(2,c\cdot\sqroot(V \log V))\) |