lecture13
lecture13
Prelude
Homework 3 is due Thursday March 14.
DFS
Last time, we started discussing depth-first search or DFS, an O(V + E) graph traversal
algorithm with nice structural properties.
Here’s a version of it that passed a clock around.
Similar to rooted trees, we can use the v.pre labels to get a preordering of the vertices
“abfgchdlokpeinjm” in that order, and the v.post labels to get a postordering
“dkoplhcgfbamjnie” in that order.
Detecting Cycles
So why did we go through defining all these things? Well, we now have the tools to solve
some real problems. And the solutions are surprisingly easy.
First, let’s suppose we’re given a directed graph G. Are there any directed cycles in G?
Lemma: Directed graph G has a cycle if and only if DFSAll(G) yields a back edge.
Suppose there is a back edge u v. Then G has a directed path from v u. That path
plus u v is a cycle.
Suppose there is a cycle. Let v be the first vertex of the cycle visited by DFSAll, and let
u v be the predecessor of v in the cycle.
I claim that DFS(v) will eventually call DFS(u).
The call to DFS(v) will reach all vertices reachable from v that don’t require going
through something already marked. In particular, the cycle itself is such a path to u
since v is the first marked vertex.
But then when DFS(u) is called, we’ll see u v is a back edge.
Edge u v is a back edge if and only if u.post < v.post. So we can compute a post
ordering in O(V + E) time and check if that’s the case for any edge u v. If not, there are
no directed cycles. It’s only O(E) more things to do after DFSAll, so still O(V + E) time total.
Topological Sort
But why do we care about directed cycles? Directed graphs without directed cycles are
called directed acyclic graphs or DAGs.
Every DAG has a topological ordering of its vertices. Formally, its a total order where u < v
if there is an edge u v. Less formally, we want to draw the vertices on a line going left to
right so there are no edges going from right to left.
The normal motivation for finding topological orderings is to decide what order to do
certain operations. Imagine we have a Makefile with several targets. We could build a
graph with targets as vertices and edges going from each target to those that depend on
it being built first. You need to compile everything in a topological order.
Topological orderings don’t exist if there are directed cycles: in any ordering the rightmost
vertex of a cycle would have an edge going back to the left.
However, if there are no directed cycles, there are no back edges after a DFSAll, meaning
u.post > v.post for every edge u v.
So, going by decreasing u.post, or reverse post ordering, you get a topological ordering!
Again, it’s just DFSAll with some extra stuff attached, so O(V + E) time.
Here, a max over nothing gets -infinity because v must not have any outgoing edges.
The dependency graph for this recurrence is the input graph G. So evaluating the function
using basic memoization is literally performing a depth-first search on G. It takes O(V + E)
time.
If we want to write it as a more standard dynamic programming algorithm, we can just fill
in each LLP value in postorder.