Chapter 5. Graphs
Classical graph traversals (BFS, DFS) and layering.
This page: Classical forms. View LLP forms »
This page collects the classical implementations of the algorithms developed in this chapter. The lattice-linear (LLP) reformulations and chapter setup live on the LLP companion page.
Strongly connected components (Kosaraju)
A strongly connected component (SCC) of a directed graph is a maximal set of vertices such that every vertex is reachable from every other. Kosaraju's algorithm finds all SCCs in two passes of DFS:
- Run DFS on the original graph and push each vertex onto a stack in order of decreasing finish time.
- Pop vertices from the stack one at a time. For each unvisited vertex, run DFS on the transpose graph (all edges reversed); the set of vertices visited in that DFS forms one SCC.
Both passes run in $O(n + m)$ time, so the total cost is $O(n + m)$.
Traversal
Generic queue-based reachability from vertex 0; matches the book's "removeAny" traversal. Returns $G[i] = 1$ iff $v_i$ is reachable.
Time complexity: $O(n + m)$, where $n$ is the number of vertices and $m$ is the number of edges.
BFS
Breadth-first search uses a FIFO queue: the source is enqueued first, and at each step
the oldest vertex is dequeued and its unvisited neighbors are enqueued. This guarantees
that vertices are visited in order of increasing distance (number of edges) from the
source, so BFS computes shortest (unweighted) distances. This implementation sets
$G[i] = $ that distance from source $s$, or Integer.MAX_VALUE if unreachable.
Time complexity: $O(n + m)$, where $n$ is the number of vertices and $m$ is the number of edges.
DFS
Depth-first search uses a stack (or recursion): at each step the most recently discovered
vertex is explored first, going as deep as possible before backtracking. DFS records
discovery and finish times, which classify edges into tree, back, forward, and cross edges
— the basis for many later algorithms. This recursive implementation tracks those
times directly; parent[] records the DFS-tree parent (-1 for the
root), and a 1-element array t rides on the recursion to share the global tick
counter.
Time complexity: $O(n + m)$, where $n$ is the number of vertices and $m$ is the number of edges.
Layering
Given a DAG, layering assigns each vertex $v$ to layer $\ell(v) = $ length of the longest path from any source to $v$; sources (in-degree zero) sit on layer 0. The classical algorithm maintains an in-degree counter for every vertex, enqueues all sources, and peels them off one layer at a time: when a vertex is removed, its successors' in-degrees are decremented, and any that reach zero are enqueued for the next layer — essentially a topological sort that additionally tracks the layer number. This implementation computes $G[j] = \ell(j)$ directly.
Time complexity: $O(n + m)$, where $n$ is the number of vertices and $m$ is the number of edges.