A Systematic Approach to Algorithms

Vijay K. Garg · The University of Texas at Austin

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:

  1. Run DFS on the original graph and push each vertex onto a stack in order of decreasing finish time.
  2. 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.