And, for the hous is crinkled to and fro, And hath so queinte weyes for to go— For hit is shapen as the mase is wroght— Therto have I a remedie in my thoght, That, by a clewe of twyne, as he hath goon, The same wey he may returne anoon, Folwing alwey the threed, as he hath come. — Geoffrey Chaucer, The Legend of Good Women (c. 8 ) “Com’è bello il mondo e come sono brutti i labirinti!” dissi sollevato. “Come sarebbe bello il mondo se ci fosse una regola per girare nei labirinti,” rispose il mio maestro. [“How beautiful the world is, and how ugly labyrinths are,” I said, relieved. “How beautiful the world would be if there were a procedure for moving through labyrinths,” my master replied.] — Umberto Eco, Il nome della rosa ( 8 ) English translation ( The Name of the Rose ) by William Weaver ( 8 ) 6 Depth-First Search In the previous chapter, we considered a generic algorithm—whatever-first search—for traversing arbitrary graphs, both undirected and directed. In this chapter, we focus on a particular instantiation of this algorithm called depth-first search , and primarily on the behavior of this algorithm in directed graphs. Although depth-first search can be accurately described as “whatever-first search with a stack”, the algorithm is normally implemented recursively, rather than using an explicit stack: DFS ( v ) : if v is unmarked mark v for each edge v w DFS ( w ) 6. D -F S We can make this algorithm slightly faster (in practice) by checking whether a node is marked before we recursively explore it. This modification ensures that we call DFS ( v ) only once for each vertex v . We can further modify the algorithm to compute other useful information about the vertices and edges, by introducing two black-box subroutines, P V and P V , which we leave unspecified for now. DFS ( v ) : mark v P V ( v ) for each edge vw if w is unmarked parent ( w ) v DFS ( w ) P V ( v ) Recall that a node w is reachable from another node v in a directed graph G — or more simply, v can reach w —if and only if G contains a directed path from v to w . Let reach ( v ) denote the set of vertices reachable from v (including v itself). If we unmark all vertices in G , and then call DFS ( v ) , the set of marked vertices is precisely reach ( v ) Reachability in undirected graphs is symmetric: v can reach w if and only if w can reach v As a result, after unmarking all vertices of an undirected graph G , calling DFS ( v ) traverses the entire component of v , and the parent pointers define a spanning tree of that component. The situation is more subtle with directed graphs, as shown in the figure below. Even though the graph is “connected”, di erent vertices can reach di erent, and potentially overlapping, portions of the graph. The parent pointers assigned by DFS ( v ) define a tree rooted at v whose vertices are precisely reach ( v ) , but this is not necessarily a spanning tree of the graph. Figure 6. Depth- rst trees rooted at different vertices in the same directed graph. As usual, we can extend our reachability algorithm to traverse the entire input graph, even if it is disconnected, using the standard wrapper function shown on the left in Figure . Here we add a generic black-box subroutine 6 6. . Preorder and Postorder P to perform any necessary preprocessing for the P V and P V functions. DFSA ( G ) : P ( G ) for all vertices v unmark v for all vertices v if v is unmarked DFS ( v ) DFSA ( G ) : P ( G ) add vertex s for all vertices v add edge s v unmark v DFS ( s ) Figure 6. Two formulations of the standard wrapper algorithm for depth- rst search Alternatively, if we are allowed to modify the graph, we can add a new source vertex s , with edges to every other vertex in G , and then make a single call to DFS ( s ) , as shown on the right of Figure . Now the resulting parent pointers always define a spanning tree of the augmented input graph, but not of the original input graph. Otherwise, the two wrapper functions have essentially identical behavior; choosing one or the other is entirely a matter of convenience. Again, this algorithm behaves slightly di erently for undirected and directed graphs. In undirected graphs, as we saw in the previous chapter, it is easy to adapt DFSA to count the components of a graph; in particular, the parent pointers computed by DFSA define a spanning forest of the input graph, containing a spanning tree for each component. When the graph is directed, however, DFSA may discover any number of “components” between 1 and V , even when the graph is “connected”, depending on the precise structure of the graph and the order in which the wrapper algorithm visits the vertices. 6. Preorder and Postorder Hopefully you are already familiar with preorder and postorder traversals of rooted trees , both of which can be computed using depth-first search. Similar traversal orders can be defined for arbitrary directed graphs—even if they are disconnected—by passing around a counter, as shown in Figure . Equiva- lently, we can use our generic depth-first-search algorithm with the following subroutines P , P V , and P V P ( G ) : clock 0 P V ( v ) : clock clock + 1 v pre clock P V ( v ) : clock clock + 1 v post clock The equivalence of these two wrapper functions is a specific feature of depth -first search. In particular, wrapping breadth -first search in a for-loop to visit every vertex does not yield the same traversal order as adding a source vertex and invoking breadth-first search at s 6. D -F S DFSA ( G ) : clock 0 for all vertices v unmark v for all vertices v if v is unmarked clock DFS ( v , clock ) DFS ( v , clock ) : mark v clock clock + 1 ; v pre clock for each edge v w if w is unmarked w parent v clock DFS ( w , clock ) clock clock + 1 ; v post clock return clock Figure 6. De ning preorder and postorder via depth- rst search. In either formulation, this algorithm assigns assigns v pre (and advances the clock) just after pushing v onto the recursion stack, and it assigns v post (and advances the clock) just before popping v o the recursion stack. It follows that for any two vertices u and v , the intervals [ u pre , u post ] and [ v pre , v post ] are either disjoint or nested. Moreover, [ u pre , u post ] contains [ v pre , v post ] if and only if DFS ( v ) is called during the execution of DFS ( u ) , or equivalently, if and only if u is an ancestor of v in the final forest of parent pointers. After DFSA labels every node in the graph, the labels v pre define a preordering of the vertices, and the labels v post define a postordering of the vertices. With a few trivial exceptions, every graph has several di erent pre- and postorderings, depending on the order that DFS considers edges leaving each vertex, and the order that DFSA considers vertices. For the rest of this chapter, we refer to v pre as the starting time of v (or less formally, “when v starts”), v post as the finishing time of v (or less formally, “when v finishes”), and the interval between the starting and finishing times as the active interval of v (or less formally, “while v is active”). Classifying Vertices and Edges During the execution of DFSA , each vertex v of the input graph has one of three states: • new if DFS ( v ) has not been called, that is, if clock < v pre ; • active if DFS ( v ) has been called but has not returned, that is, if v pre clock < v post ; • finished if DFS ( v ) has returned, that is, if v post clock Because starting and finishing times correspond to pushes and pops on the recursion stack, a vertex is active if and only if it is on the recursion stack. It follows that the active nodes always comprise a directed path in G Confusingly, both of these orders are sometimes called “depth-first ordering”. Please don’t do that. 8 6. . Preorder and Postorder b f g c h d o k p e i n j m l a 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 a b c d e f g h i j k l m n o p 1 2 Figure 6. A depth- rst forest of a directed graph, and the corresponding active intervals of its vertices, de ning the preordering abfgchdlokpeinjm and the postordering dkoplhcgfbamjnie . Forest edges are solid; dashed edges are explained in Figure 6. The edges of the input graph fall into four di erent classes, depending on how their active intervals intersect. Fix your favorite edge u v • If v is new when DFS ( u ) begins, then DFS ( v ) must be called during the execution of DFS ( u ) , either directly or through some intermediate recursive calls. In either case, u is a proper ancestor of v in the depth-first forest, and u pre < v pre < v post < u post – If DFS ( u ) calls DFS ( v ) directly, then u = v parent and u v is called a tree edge – Otherwise, u v is called a forward edge • If v is active when DFS ( u ) begins, then v is already on the recursion stack, which implies the opposite nesting order v pre < u pre < u post < v post Moreover, G must contain a directed path from v to u . Edges satisfying this condition are called back edges • If v is finished when DFS ( u ) begins, we immediately have v post < u pre Edges satisfying this condition are called cross edges • Finally, the fourth ordering u post < v pre is impossible. These edge classes are illustrated in Figure . Again, the actual classification of edges depends on the order in which DFSA considers vertices and the order in which DFS considers the edges leaving each vertex. 6. D -F S back forward tree cross back forward cross tree s t v u w s t u v w 1 2 3 4 5 6 7 8 9 10 Figure 6. Classi cation of edges by depth- rst search. Finally, the following key lemma characterizes ancestors and descendants in any depth-first forest according to vertex states during the traversal. Lemma Fix an arbitrary depth-first traversal of any directed graph G . The following statements are equivalent for all vertices u and v of G (a) u is an ancestor of v in the depth-first forest. (b) u pre v pre < v post u post (c) Just after DFS ( v ) is called, u is active. (d) Just before DFS ( u ) is called, there is a path from u to v in which every vertex (including u and v ) is new. Proof: First, suppose u is an ancestor of v in the depth-first forest. Then by definition there is a path P of tree edges u to v By induction on the path length, we have u pre w pre < w post u post for every vertex w in P , and thus every vertex in P is new before DFS ( u ) is called. In particular, we have u pre v pre < v post u post , which implies that u is active while DFS ( v ) is executing. Because parent pointers correspond to recursive calls, u pre v pre < v post u post implies that u is an ancestor of v Suppose u is active just after DFS ( v ) is called. Then u pre v pre < v post u post , which implies that there is a path of (zero or more) tree edges from u , through the intermediate nodes on the recursion stack (if any), to v Finally, suppose u is not an ancestor of v . Fix an arbitrary path P from u to v , let x be the first vertex in P that is not a descendant of u , and let w be the predecessor of x in P . The edge w x guarantees that x pre < w post , and w post < u post because w is a descendant of u , so x pre < u post . It follows that x pre < u pre , because otherwise x would be a descendant of u . Because active intervals are properly nested, there are only two possibilities: • If u post < x post , then x is active when DFS ( u ) is called. • If x post < u pre , then x is already finished when DFS ( u ) is called. 6. . Detecting Cycles We conclude that every path from u to v contains a vertex that is not new when DFS ( u ) is called. É 6. Detecting Cycles A directed acyclic graph or dag is a directed graph with no directed cycles. Any vertex in a dag that has no incoming vertices is called a source ; any vertex with no outgoing edges is called a sink . An isolated vertex with no incident edges at all is both a source and a sink. Every dag has at least one source and one sink, but may have more than one of each. For example, in the graph with n vertices but no edges, every vertex is a source and every vertex is a sink. a b c d e f g h i j k l m n o p Figure 6.6. A directed acyclic graph. Vertices e , f , and j are sources; vertices b , c , and p are sinks. Recall from our earlier case analysis that if u post < v post for any edge u v , the graph contains a directed path from v to u , and therefore contains a directed cycle through the edge u v . Thus, we can determine whether a given directed graph G is a dag in O ( V + E ) time by computing a postordering of the vertices and then checking each edge by brute force. Alternatively, instead of numbering the vertices, we can explicitly maintain the status of each vertex and immediately return F if we ever discover an edge to an active vertex. This algorithm also runs in O ( V + E ) time; see Figure I A ( G ) : for all vertices v v status N for all vertices v if v status = N if I A DFS ( v ) = F return F return T I A DFS ( v ) : v status A for each edge v w if w status = A return F else if w status = N if I A DFS ( w ) = F return F v status F return T Figure 6. A linear-time algorithm to determine if a graph is acyclic. 6. D -F S 6. Topological Sort A topological ordering of a directed graph G is a total order on the vertices such that u v for every edge u v Less formally, a topological ordering arranges the vertices along a horizontal line so that all edges point from left to right. A topological ordering is clearly impossible if the graph G has a directed cycle—the rightmost vertex of the cycle would have an edge pointing to the left! On the other hand, consider an arbitrary postordering of an arbitrary directed graph G . Our earlier analysis implies that u post < v post for any edge u v , then G contains a directed path from v to u , and therefore contains a directed cycle through u v . Equivalently, if G is acyclic, then u post > v post for every edge u v . It follows that every directed acyclic graph G has a topological ordering; in particular, the reversal of any postordering of G is a topological ordering of G a b c d e f g h i j k l m n o p b f g c h d o k p e i n j m l a 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 1 2 3 4 5 6 a b c d e f g h i j k m n p l o Figure 6.8. Reversed postordering of the dag from Figure 6.6. If we require the topological ordering in a separate data structure, we can simply write the vertices into an array in reverse postorder, in O ( V + E ) time, as shown in Figure Implicit Topological Sort But recording the topological order into a separate data structure is usually overkill. In most applications of topological sort, the ordered list of the vertices is not our actual goal; rather, we want to perform some fixed computation at each vertex of the graph, either in topological order or in reverse topological order. For these applications, it is not necessary to record the topological order at all! 6. . Topological Sort T S ( G ) : for all vertices v v status N clock V for all vertices v if v status = N clock T S DFS ( v , clock ) return S [ 1 .. V ] T S DFS ( v , clock ) : v status A for each edge v w if w status = N clock T S DFS ( v , clock ) else if w status = A fail gracefully v status F S [ clock ] v clock clock 1 return clock Figure 6. Explicit topological sort If we want to process a directed acyclic graph in reverse topological order, it su ces to process each vertex at the end of its recursive depth-first search. After all, topological order is the same as reversed postorder! P P ( G ) : for all vertices v v status N for all vertices v if v is unmarked P P DFS ( v ) P P DFS ( v ) : v status A for each edge v w if w status = N P P DFS ( w ) else if w status = A fail gracefully v status F P ( v ) If we already know that the input graph is acyclic, we can further simplify the algorithm by simply marking vertices instead of recording their search status. P P D ( G ) : for all vertices v unmark v for all vertices v if v is unmarked P P D DFS ( s ) P P D DFS ( v ) : mark v for each edge v w if w is unmarked P P D DFS ( w ) P ( v ) This is just the standard depth-first search algorithm, with P V renamed to P ! Because it is such a common operation on directed acyclic graphs, I sometimes express postorder processing of a dag idiomatically as follows: P P D ( G ) : for all vertices v in postorder P ( v ) 6. D -F S For example, our earlier explicit topological sort algorithm can be written as follows: T S ( G ) : clock V for all vertices v in postorder S [ clock ] v clock clock 1 return S [ 1 .. V ] To process a dag in forward topological order, we can record a topological ordering of the vertices into an array and then run a simple for-loop. Alternatively, we can apply depth-first search to the reversal of G , denoted rev ( G ) , obtained by replacing each each v w with its reversal w v . Reversing a directed cycle gives us another directed cycle with the opposite orientation, so the reversal of a dag is another dag. Every source in G is a sink in rev ( G ) and vice versa; it follows inductively that every topological ordering of rev ( G ) is the reversal of a topological ordering of G The reversal of any directed graph (represented in a standard adjacency list) can be computed in O ( V + E ) time; the details of this construction are left as an easy exercise. 6. Memoization and Dynamic Programming Our topological sort algorithm is arguably the model for a wide class of dynamic programming algorithms. Recall that the dependency graph of a recurrence has a vertex for every recursive subproblem and an edge from one subproblem to another if evaluating the first subproblem requires a recursive evaluation of the second. The dependency graph must be acyclic, or the naïve recursive algorithm would never halt. Evaluating any recurrence with memoization is exactly the same as perform- ing a depth-first search of the dependency graph. In particular, a vertex of the dependency graph is “marked” if the value of the corresponding subproblem has already been computed. The black-box subroutines P V and P V are proxies for the actual value computation. See Figure Carrying this analogy further, evaluating a recurrence using dynamic pro- gramming is the same as evaluating all subproblems in the dependency graph of the recurrence in reverse topological order—every subproblem is considered after the subproblems it depends on. Thus, every dynamic programming al- gorithm is equivalent to a postorder traversal of the dependency graph of its underlying recurrence! A postordering of the reversal of G is not necessarily the reversal of a postordering of G , even though both are topological orderings of G 6. . Memoization and Dynamic Programming M ( x ) : if value [ x ] is undefined initialize value [ x ] for all subproblems y of x M ( y ) update value [ x ] based on value [ y ] finalize value [ x ] DFS ( v ) : if v is unmarked mark v P V ( x ) for all edges v w DFS ( w ) P V ( x ) Figure 6. Memoized recursion is depth- rst search. Depth- rst search is memoized recursion. D P ( G ) : for all subproblems x in postorder initialize value [ x ] for all subproblems y of x update value [ x ] based on value [ y ] finalize value [ x ] Figure 6. Dynamic programming is postorder traversal. However, there are some minor di erences between most dynamic program- ming algorithms and topological sort. First, in most dynamic programming algorithms, the dependency graph is implicit —the nodes and edges are not explicitly stored in memory, but rather are encoded by the underlying recur- rence. But this di erence really is minor; as long as we can enumerate recursive subproblems in constant time each, we can traverse the dependency graph exactly as if it were explicitly stored in an adjacency list. More significantly, most dynamic programming recurrences have highly structured dependency graphs. For example, as we discussed in Chapter , the dependency graph for the edit distance recurrence is a regular grid with diagonals, and the dependency graph for optimal binary search trees is an upper triangular grid with all possible rightward and upward edges. This regular structure allows us to hard-wire a suitable evaluation order directly into the algorithm, typically as a collection of nested loops, so there is no need to topologically sort the dependency graph at run time. We previously called the reverse topological order an evaluation order Dynamic Programming in Dags Conversely, we can use depth-first search to build dynamic programming algorithms for problems with less structured dependency graphs. For example, consider the longest path problem, which asks for the path of maximum total weight from one node s to another node t in a directed graph G with weighted edges. In general directed graphs, the longest path problem is NP-hard (by an easy reduction from the traveling salesman problem; see Chapter ), but it is 6. D -F S Figure 6. The dependency dag of the edit distance recurrence. easy to if the input graph G is acyclic, we can compute the longest path in G in linear time, as follows. Fix the target vertex t , and for any node v , let LLP ( v ) denote the Length of the Longest Path in G from v to t . If G is a dag, this function satisfies the recurrence LLP ( v ) = ® 0 if v = t , max ` ( v w ) + LLP ( w ) v w 2 E otherwise, where ` ( v w ) denotes the given weight (“length”) of edge v w , and max ? = 1 . In particular, if v is a sink but not equal to t , then LLP ( v ) = 1 The dependency graph for this recurrence is the input graph G itself: subproblem LLP ( v ) depends on subproblem LLP ( w ) if and only if v w is an edge in G . Thus, we can evaluate this recursive function in O ( V + E ) time by performing a depth-first search of G , starting at s . The algorithm memoizes each length LLP ( v ) into an extra field in the corresponding node v L P ( v , t ) : if v = t return 0 if v LLP is undefined v LLP 1 for each edge v w v LLP max v LLP , ` ( v w ) + L P ( w , t ) return v LLP In principle, we can transform this memoized recursive algorithm into a dynamic programming algorithm via topological sort: 6 6. . Strong Connectivity L P ( s , t ) : for each node v in postorder if v = t v LLP 0 else v LLP 1 for each edge v w v LLP max v LLP , ` ( v w ) + w LLP return s LLP These two algorithms are arguably identical—the recursion in the first algorithm and the for-loop in the second algorithm represent the “same” depth-first search! Choosing one of these formulations over the other is entirely a matter of convenience. Almost any dynamic programming problem that asks for an optimal sequence of decisions can be recast as finding an optimal path in some associated dag. For example, the text segmentation, subset sum, longest increasing subsequence, and edit distance problems we considered in Chapters and can all be reformulated as finding either a longest path or a shortest path in a dag, possibly with weighted vertices or edges. In each case, the dag in question is the dependency graph of the underlying recurrence. On the other hand, “tree- shaped” dynamic programming problems, like finding optimal binary search trees or maximum independent sets in trees, cannot be recast as finding an optimal path in a dag. 6. Strong Connectivity Let’s go back to the proper definition of connectivity in directed graphs. Recall that one vertex u can reach another vertex v in a directed graph G if G contains a directed path from u to v , and that reach ( u ) denotes the set of all vertices that u can reach. Two vertices u and v are strongly connected if u can reach v and v can reach u . A directed graph is strongly connected if and only if every pair of vertices is strongly connected. Tedious definition-chasing implies that strong connectivity is an equivalence relation over the set of vertices of any directed graph, just like connectivity in undirected graphs. The equivalence classes of this relation are called the strongly connected components —or more simply, the strong components —of G . Equiv- alently, a strong component of G is a maximal strongly connected subgraph of G . A directed graph G is strongly connected if and only if G has exactly one strong component; at the other extreme, G is a dag if and only if every strong component of G consists of a single vertex. The strong component graph scc ( G ) is another directed graph obtained from G by contracting each strong component to a single vertex and collapsing 6. D -F S parallel edges. (The strong component graph is sometimes also called the meta-graph or condensation of G .) It’s not hard to prove (hint, hint) that scc ( G ) is always a dag. Thus, at least in principle, it is possible to topologically order the strong components of G ; that is, the vertices can be ordered so that every back edge joins two edges in the same strong component. a b c d e f g h i j k l m n o p a b f g e i j m n p c d h k l o Figure 6. The strong components of a graph G and the strong component graph scc ( G ) It is straightforward to compute the strong component of a single vertex v in O ( V + E ) time. First we compute reach ( v ) via whatever-first search. Then we compute reach 1 ( v ) = { u | v 2 reach ( u ) } by searching the reversal of G Finally, the strong component of v is the intersection reach ( v ) \ reach 1 ( v ) . In particular, we can determine whether the entire graph is strongly connected in O ( V + E ) time. Similarly, we can compute all the strong components in a directed graph by combining the previous algorithm with our standard wrapper function. However, the resulting algorithm runs in O ( V E ) time; there are at most V strong components, and each requires O ( E ) time to discover, even when the graph is a dag. Surely we can do better! After all, we only need O ( V + E ) time to decide whether every strong component is a single vertex. 6.6 Strong Components in Linear Time In fact, there are several algorithms to compute strong components in O ( V + E ) time, all of which rely on the following observation. Lemma Fix a depth-first traversal of any directed graph G . Each strong component C of G contains exactly one node that does not have a parent in C (Either this node has a parent in another strong component, or it has no parent.) Proof: Let C be an arbitrary strong component of G . Consider any path from one vertex v 2 C to another vertex w 2 C . Every vertex on this path can reach w , and thus can reach every vertex in C ; symmetrically, every node on this path can be reached by v , and thus can be reached by every vertex in C . We conclude that every vertex on this path is also in C 8 6.6. Strong Components in Linear Time Let v be the vertex in C with the earliest starting time. If v has a parent, then parent ( v ) starts before v and thus cannot be in C Now let w be another vertex in C . Just before DFS ( v ) is called, every vertex in C is new, so there is a path of new vertices from v to w . Lemma now implies that w is a descendant of v in the depth-first forest. Every vertex on the path of tree edges v to w lies in C ; in particular, parent ( w ) 2 C É The previous lemma implies that each strong component of a directed graph G defines a connected subtree of any depth-first forest of G . In particular, for any strong component C , the vertex in C with the earliest starting time is the lowest common ancestor of all vertices in C ; we call this vertex the root of C b f g h d o k n j m l 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 a b c d e f g h i j k l m n o p 1 2 c p e i a Figure 6. Strong components are contiguous in the depth- rst forest. I’ll present two algorithms, both of which follow the same intuitive outline. Let C be any strong component of G that is a sink in scc ( G ) ; we call C a sink component Equivalently, C is a sink component if the reach of any vertex in C is precisely C . We can find all the strong components in G by repeatedly finding a vertex v in some sink component (somehow), finding the vertices reachable from v , and removing that sink component from the input graph, until no vertices remain. This isn’t quite an algorithm yet, because it’s not clear how to find a vertex in a sink component! S C ( G ) : count 0 while G is non-empty C ? count count + 1 v any vertex in a sink component of G hh Magic! ii for all vertices w in reach ( v ) w label count add w to C remove C and its incoming edges from G Figure 6. Almost an algorithm to compute strong components. 6. D -F S Kosaraju and Sharir’s Algorithm At first glance, finding a vertex in a sink component quickly seems quite di cult. However, it’s actually quite easy to find a vertex in a source component—a strong component of G that corresponds to a source in scc ( G ) —using depth-first search. Lemma The last vertex in any postordering of G lies in a source component of G Proof: Fix a depth-first traversal of G , and let v be the last vertex in the resulting postordering. Then DFS ( v ) must be the last direct call to DFS made by the wrapper algorithm DFSA Moreover, v is the root of one of the trees in the depth-first forest, so any node x with x post > v pre is a descendant of v Finally, v is the root of its strong component C For the sake of argument, suppose there is an edge x y such that x 6 2 C and y 2 C . Then x can reach y , and y can reach v , so x can reach v . Because v is the root of C , vertex y is a descendant of v , and thus v pre < y pre . The edge x y guarantees that y pre < x post and therefore v pre < x post . It follows that x is a descendant of v But then v can reach x (through tree edges), contradicting our assumption that x 6 2 C É It is easy to check (hint, hint) that rev ( scc ( G )) = scc ( rev ( G )) for any directed graph G . Thus, the last vertex in a postordering of rev ( G ) lies in a sink component of the original graph G . Thus, if we traverse the graph a second time, where the wrapper function follows a reverse postordering of rev ( G ) , then each call to DFS visits exactly one strong component of G Putting everything together, we obtain the algorithm shown in Figure , which counts and labels the strong components of any directed graph in O ( V + E ) time. This algorithm was discovered (but never published) by Rao Kosaraju in , and later independently rediscovered by Micha Sharir in The Kosaraju-Sharir algorithm has two phases. The first phase performs a depth-first search of rev ( G ) , pushing each vertex onto a stack when it is finished. In the second phase, we perform a whatever -first traversal of the original graph G , considering vertices in the order they appear on the stack. The algorithm labels each vertex with the root of its strong component (with respect to the second depth-first traversal). Figure shows the Kosaraju-Sharir algorithm running on our example graph. With only minor modifications to the algorithm, we can also compute the strong component graph scc ( G ) in O ( V + E ) time. Again: A reverse postordering of rev ( G ) is not the same as a postordering of G There are rumors that the same algorithm appears in the Russian literature even before Kosaraju, but I haven’t found a reliable source for that rumor yet. 6.6. Strong Components in Linear Time K S ( G ) : S new empty stack for all vertices v unmark v v root N hh Phase : Push in postorder in rev(G) ii for all vertices v if v is unmarked P P R DFS ( v , S ) hh Phase : DFS again in stack order ii while S is non-empty v P ( S ) if v root = N L O DFS ( v , v ) P P R DFS ( v , S ) : mark v for each edge u v hh Reversed! ii if u is unmarked P P R DFS ( u , S ) P ( v , S ) L O DFS ( v , r ) : v root r for each edge v w if w root = N L O DFS ( w , r ) Figure 6. 6. The Kosaraju-Sharir strong components algorithm b f g c h d o k p e i n j m l a 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 a b c d e f g h i j k l m n o p 12 16 2 3 15 14 13 4 10 8 5 7 11 9 6 1 1 2 3 1 2 3 4 5 b f g c h d o k p e i n j m l a 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 Figure 6. The Kosaraju-Sharir algorithm in action. Top: Depth- rst traversal of the reversed graph. Bottom: Depth- rst traversal of the original graph, visiting root vertices in reversed postorder from the rst traversal. 6. D -F S ™ Tarjan’s Algorithm An earlier but considerably more subtle linear-time algorithm to compute strong components was published by Bob Tarjan in Intuitively, Tarjan’s algorithm identifies a source component of G , “deletes” it, and then “recursively” finds the remaining strong components; however, the entire computation happens during a single depth-first search. Fix an arbitrary depth-first traversal of some directed graph G . For each vertex v , let low ( v ) denote the smallest starting time among all vertices reachable from v by a path of tree edges followed by at most one non-tree edge. Trivially, low ( v ) v pre , because v can reach itself through zero tree edges followed by zero non-tree edges. Tarjan observed that sink components can be characterized in terms of this low function. Lemma A vertex v is the root of a sink component of G if and only if low ( v ) = v pre and low ( w ) < w pre for every proper descendant w of v Proof: First, let v be a vertex such that low ( v ) = v pre . Then there is no edge w x where w is a descendant of v and x pre < v pre . On the other hand, v cannot reach any vertex y such that y pre > v post It follows that v can reach only its descendants, and therefore any descendant of v can reach only descendants of v . In particular, v cannot reach its parent (if it has one), so v is the root of its strong component. Now suppose in addition that low ( w ) < w pre for every descendant w of v Then each descendant w can reach another vertex x (which must be another descendant of v ) such that x pre < w pre . Thus, by induction, every descendant of v can reach v It follows that the descendants of v comprise the strong component C whose root is v . Moreover, C must be a sink component, because v cannot reach any vertex outside of C On the other hand, suppose v is the root of a sink component C . Then v can reach another vertex w if and only if w 2 C But v can reach all of its descendants, and every vertex in C is a descendant of v , so v ’s descendants comprise C If low ( w ) = w pre for any other node w 2 C , then w would be another root of C , which is impossible. É Computing low ( v ) for every vertex v via depth-first search is straightforward; see Figure Lemma implies that after running F L , we can identify the root of every sink component in O ( V + E ) time (by a global whatever-first search), According to legend, Kosaraju apparently discovered his algorithm during an algorithms lecture. He was supposed to present Tarjan’s algorithm, but he forgot his notes, so he had to make up something else on the fly. The only aspect of this story that I find surprising is that nobody tells it about Sharir or Tarjan. 6.6. Strong Components in Linear Time F L ( G ) : clock 0 for all vertices v unmark v for all vertices v if v is unmarked F L DFS ( v ) F L DFS ( v ) : mark v clock clock + 1 v pre clock v low v pre for each edge v w if w is unmarked F L DFS ( w ) v low min { v low , w low } else v low min { v low , w pre } Figure 6. 8. Computing low ( v ) for every vertex v and then mark and delete those sink components in O ( V + E ) additional time (by calling whatever-first search at each root), and then recurse. Unfortunately, the resulting algorithm might require V iterations, each removing only a single vertex, naively giving us a total running time of O ( V E ) To speed up this strategy, Tarjan’s algorithm maintains an auxiliary stack of vertices (separate from the recursion stack). Whenever we start a new vertex v , we push it onto the stack. Whenever we finish a vertex v , we compare v low with v pre . Then the first time we discover that v low = v pre , we know three things: • Vertex v is the root of a sink component C • All vertices in C appear consecutively at the top of the auxiliary stack. • The deepest vertex in C on the auxiliary stack is v At this point, we can identify the vertices in C by popping them o the auxiliary stack one by one, stopping when we pop v We could delete the vertices in C and recursively compute the strong components of the remaining graph, but that would be wasteful, because we would repeat verbatim all computation done before visiting v Instead, we label each vertex in C , identifying v as the root of its strong component, and then ignore labeled vertices for the rest of the depth-first search. Formally, this modification changes the definition of low ( v ) to the smallest starting time among all vertices in the same strong component as v that v can reach by a path of tree edges followed by at most one non-tree edge. But to prove correctness, it’s easier to observe that ignoring labeled vertices leads the algorithm to exactly the same behavior as actually deleting them. Finally, Tarjan’s algorithm is shown in Figure , with the necessary modifications from F L (Figure ) indicated in bold red. The running time of the algorithm can be split into two parts. Each vertex is pushed onto S once and popped o S once, so the total time spent maintaining the auxiliary stack (the red stu ) is O ( V ) . If we ignore the auxiliary stack maintenance, the 6. D -F S rest of the algorithm is just a standard depth-first search. We conclude that the algorithm runs in O ( V + E ) time T ( G ) : clock 0 S new empty stack for all vertices v unmark v v root N for all vertices v if v is unmarked T DFS ( v ) T DFS ( v ) : mark v clock clock + 1 v pre clock v low v pre P ( S , v ) for each edge v w if w is unmarked T DFS ( w ) v low min { v low , w low } else if w root = N v low min { v low , w pre } if v low = v pre repeat w P ( S ) w root v until w = v Figure 6. Tarjan’s strong components algorithm. Exercises Depth- rst search, topological sort, and strong components . (a) Describe an algorithm to compute the reversal rev ( G ) of a directed graph in O ( V + E ) time. (b) Prove that for every directed graph G , the strong component graph scc ( G ) is acyclic. (c) Prove that scc ( rev ( G )) = rev ( scc ( G )) for every directed graph G (d) Fix an arbitrary directed graph G . For any vertex v of G , let S ( v ) denote the strong component of G that contains v . For all vertices u and v of G , prove that u can reach v in G if and only if S ( u ) can reach S ( v ) in scc ( G ) A