Algorithms II: Graphs, Dynamic Programming, Greedy & Flows
Most optimization problems are a graph in disguise — schedules, dependencies, assignments, routes — and the art is recognizing which structure and which paradigm fits. Graph search (DFS/BFS) is the substrate: finish-time order powers topological sort and strongly-connected components; BFS layers give unweighted shortest paths. On top sit three optimization paradigms. Dynamic programming handles overlapping subproblems by tabulating them — and the flagship shortest-path algorithms (Bellman–Ford, Floyd–Warshall) are DP on graphs. Greedy makes one locally-optimal choice and never reconsiders — fast and simple when the substructure is nice, but it must be proven correct, usually by an exchange argument; Dijkstra, Prim, and Kruskal (MST) are greedy on graphs. Flow turns cuts, matchings, and selection problems into max-flow via the max-flow–min-cut duality. The recurring decision is the spine: DP if subproblems overlap, greedy if one choice leaves one clean subproblem, flow if it’s about separating or pairing. Prerequisites cashed in: DFS/BFS and heaps, recurrences and induction, the decomposition method and Monte-Carlo idea (13a); indicator RVs and geometric trials (track 1).
1. Graph foundations and search
-
Setup. , , . Store as an adjacency list ( space, good for sparse graphs — the default) or an adjacency matrix (). Undirected: connected components. Directed: strongly connected components (§ below) — don’t conflate the two.
-
Depth-first search. Go as deep as possible, mark a node done, backtrack to the nearest unexplored neighbor — DFS implicitly builds a tree (a forest if you must restart at unreached vertices). Track a start and finish time per node. Runtime: each vertex plus , so .
- Parentheses theorem: in the DFS forest the time-intervals nest — if is a descendant of then ; otherwise the intervals are disjoint.
- Topological sort (DAG): for any edge , , so listing vertices in decreasing finish time respects every dependency. — just DFS, prepend each vertex as it finishes.
-
Breadth-first search. Explore by distance: a FIFO queue, peeling off layers . Also .
- Shortest path (unweighted): a vertex’s layer index is its distance from the source (init dist 0 at source, elsewhere; each neighbor is parent-distance + 1).
- Bipartite test: 2-color by layer parity; a graph is bipartite iff this never puts an edge within a color — equivalently iff it has no odd cycle.
-
Strongly connected components. An SCC is a maximal set with a directed path both ways between every pair. Contract each SCC to a meta-vertex → the SCC graph is a DAG (no cycles between SCCs). Kosaraju’s algorithm, :
- DFS on , record finish times.
- Reverse all edges (this leaves SCCs unchanged).
- DFS on , starting from the largest finish time first; each tree of this second forest is one SCC.
Why: the key lemma — in the forward graph, if there’s an edge from SCC to SCC then — so the largest finish time lies in a “source” SCC of the DAG, and exploring the reversed graph from it can’t escape that SCC. Picture (draw it): circle each SCC and contract it to one node — the result is a DAG with source and sink SCCs; the largest overall finish time always lands in a source SCC, so peeling off finish-times-descending on the reversed graph carves out exactly one SCC per DFS tree.
Core competency set
- Give adjacency-list vs matrix tradeoff; state DFS/BFS as .
- Topological sort by decreasing finish time; BFS for unweighted shortest path and bipartiteness (odd cycle).
- State Kosaraju’s three steps and the finish-time key lemma.
2. Shortest paths
- Dijkstra — greedy, nonnegative weights, single-source. Maintain a set of finalized vertices; repeatedly extract the vertex with the smallest distance estimate, add it to , and relax its outgoing edges (update each neighbor’s estimate to ).
- Why greedy is safe: with nonnegative weights, the closest unfinalized vertex can’t be improved by a detour through a farther one — so its estimate is already final when extracted.
- Each vertex is extracted once (insert + extract-min, times) with a decrease-key per edge → with a heap, . (Negative weights break the greedy guarantee — use Bellman–Ford.)
- Numeric anchor: edges . Finalize in nondecreasing order: ; relax → ; extract , relax → ; extract , relax → ; extract . Shortest via — note each vertex is finalized at a value the previous one, the greedy invariant in action.
- Bellman–Ford — DP, tolerates negative edges (no negative cycles). The optimal substructure budgets the output: shortest-path cost using edges, with recurrence
(the last hop came from some neighbor ). A shortest path is simple (no negative cycle ⇒ no beneficial cycle), so it has edges; iterate up to . Negative-cycle detection: if any estimate still improves at iteration , a negative cycle is reachable. ; only the previous row is needed, and you can skip vertices that didn’t change.
- Floyd–Warshall — DP, all-pairs shortest paths. Substructure indexes the allowed intermediate vertices: shortest path using only intermediates in . Adding vertex either helps or doesn’t:
Three nested loops → , beating runs of Bellman–Ford (). (Johnson’s algorithm reweights with a dummy source so Dijkstra can be run from every vertex for APSP on sparse graphs.)
Core competency set
- Dijkstra: greedy procedure, why nonnegativity is required, .
- Bellman–Ford recurrence (budget = edges, ), negative-cycle detection, .
- Floyd–Warshall recurrence (budget = intermediate set), .
3. Dynamic programming
- What it is. Tabulate solutions to overlapping subproblems (unlike divide & conquer’s disjoint ones), filling cells in an order where each depends only on earlier cells, so each is computed once. Runtime (#subproblems) (work per subproblem) + postprocessing — the number of subproblems lower-bounds the runtime.
- The recipe: (1) identify the optimal substructure — express the big solution via smaller ones, usually by casing on the last decision; (2) write the recurrence for the optimal value; (3) compute it bottom-up (nested loops, small problems first) or top-down (recursion + memoization — update the cache before returning); (4) reconstruct the actual solution by backtracking through the table. Mental trick: jump into the middle and imagine being handed the optimal solution up to index .
- 0/1 knapsack — items with weights , values , capacity ; each item used . Let = max value from the first items at capacity . Casing on item (skip vs take):
Fill over items then capacities → ; reconstruct by checking whether each came from “above” (skipped) or “up-and-left” (took it). (Unbounded knapsack drops the item index: .)
- Numeric anchor: items , capacity . The table fills to , achieved by items 2+3 (weight , value ) — beating items 1+4 (value 8). Picture (draw it): the DP grid, rows = items down, columns = capacities across; every cell looks only up (skip item ) or up-and--left (take it), so you sweep row by row and read the answer at the bottom-right corner, then follow those two arrows back to recover which items were taken.
- Longest common subsequence — = LCS length of prefixes . Case on the last characters:
to fill, to recover the subsequence by backtracking.
Core competency set
- State the DP recipe (optimal substructure → recurrence → bottom-up/memoized → reconstruct) and bottom-up vs top-down.
- Derive the 0/1 knapsack and LCS recurrences by casing on the last decision, with their runtimes.
4. Greedy algorithms and minimum spanning trees
- The paradigm. Build a solution with locally-optimal choices, never reconsidering — top-down (choose, then one subproblem remains). Fast and simple, but most greedy algorithms are wrong, so correctness is the whole challenge.
- Exchange argument (the standard correctness proof). Invariant: after greedy choices, some optimal solution still extends them. Base: before any choice, an optimal solution exists. Step: given an optimal extending the first choices, if our -th choice differs from ‘s, swap it in for ‘s element and show the result is still feasible and no worse. At the end no choices remain, so our solution is optimal.
- Activity selection — most non-overlapping activities. Greedily pick the one with the earliest finish time, then repeat among the compatible rest. (Earliest finish leaves the most room; exchange proof: it ends no later than ‘s pick, so it can’t conflict with anything scheduled after.) (sort by finish).
- Job scheduling — minimize total weighted completion time ( completion-time). Order by the ratio descending (neither cost nor time alone). Exchange: swapping an adjacent out-of-order pair only lowers cost.
- Huffman codes — a prefix-free binary code minimizing (frequency depth). Greedily merge the two least-frequent nodes into a parent (a min-priority queue), repeat. Lemma: the two least-frequent symbols are siblings at max depth in some optimal tree.
- Minimum spanning tree. A spanning tree is an acyclic edge subset connecting all vertices; want minimum total cost. Everything rests on the cut lemma: given a cut (a vertex partition) that respects the chosen edge set (no -edge crosses it), and the light edge (cheapest crossing the cut), there is an MST containing . So you can always safely add the cheapest edge crossing a cut — that’s the greedy choice both MST algorithms make.
- Prim — grow one tree from a start vertex, repeatedly adding the cheapest edge crossing the frontier (tree vs ). Heap-keyed by each outside vertex’s cheapest edge to the tree → . (Like Dijkstra, but the key is edge cost to the tree, not distance to a source.)
- Kruskal — sort all edges, add the cheapest that doesn’t form a cycle (growing a forest of trees that merge). Cycle check = “are the endpoints already connected?” via union-find.
- Union-find — maintains a partition; each element points to a parent, Find walks to the root (the set’s name), Union attaches the smaller tree under the larger (union by size) to keep depth . A cycle would join two endpoints already sharing a root. This brings Kruskal to .
Core competency set
- State the greedy paradigm and prove correctness by exchange argument (the “extends our choices” invariant).
- Give activity selection (earliest finish), job scheduling (cost/time ratio), Huffman (merge least frequent).
- State the MST cut lemma and how Prim and Kruskal each apply it; explain union-find (Find/Union by size) for cycle detection.
5. Max-flow / min-cut
- Flow network. Directed with edge capacities ; a flow obeys the capacity constraint and conservation (in = out at every vertex but ). Value = net flow out of the source .
- s–t cut with : its capacity is the sum of capacities of edges only. Weak duality: any flow any cut, since all flow must cross the divide — .
- Max-flow–min-cut theorem: . The three conditions are equivalent: (1) is maximum; (2) the residual network has no augmenting path; (3) for some cut.
- Ford–Fulkerson method. Start ; while there’s an augmenting path in the residual network , push the bottleneck residual capacity along it. The residual network has, for each edge, a forward edge of leftover capacity and a reverse edge of capacity (so flow can be cancelled — sending flow backward undoes an earlier bad choice). Augmenting on a forward edge raises its flow, on a backward edge lowers it. Terminates when is unreachable from in — exactly when is max. With integer capacities, (can be slow for large capacities).
- Numeric anchor: . Augment (push 2, saturates ), then (push 2, saturates ), then on the residual (push 1) → . The cut has capacity , so -flow -cut (and is now unreachable from in the residual — the termination certificate). Picture (draw it): the – network with a dashed vertical cut separating from (only forward edges count toward capacity); beside it the residual graph — each used edge keeps a forward stub of size and gains a backward arrow of size for cancellation.
- Edmonds–Karp — choose the augmenting path by BFS (fewest edges); runtime becomes , independent of capacities.
- Bipartite matching as flow. Orient all graph edges , add a source every and every sink, all unit capacity. An integer max flow decomposes into disjoint unit – paths, each a matched pair, so max matching = max flow (and integer capacities guarantee an integer/0–1 flow). The model fits assignment problems generally.
- Project selection (min-cut application): infinite-capacity edges from prerequisite to task can never be cut; connect positive-reward tasks and negative-reward tasks ; the min cut selects the optimal feasible subset.
- Global min cut — Karger (undirected, the fewest edges whose removal disconnects ; not an – cut). Contraction algorithm: repeatedly pick a random edge and contract it (merge its endpoints into a supernode, keeping parallel edges) until two supernodes remain — they define a cut. A Monte Carlo algorithm (always fast, sometimes wrong — it fails iff it ever contracts a min-cut edge).
- Success probability : if the min cut has size , every vertex has degree , so has edges; after contractions edges remain, so the chance of contracting a min-cut edge is . The product of survival probabilities telescopes:
Repeat times to fail with probability (since ). Karger–Stein notices early contractions are safe and only re-runs the risky late stages, recursing once the graph is down to nodes: .
Core competency set
- Define a flow, an – cut, and state weak duality and the max-flow–min-cut theorem (three equivalences).
- Explain Ford–Fulkerson with residual graphs (forward + reverse/cancellation edges) and Edmonds–Karp’s BFS choice.
- Model bipartite matching as unit-capacity flow; state Karger’s contraction and derive .
6. Stable matching
- Setup. doctors, hospitals, each with a strict preference list. A blocking pair both prefer each other to their assignments — they’d defect. A stable matching has no blocking pair (no pair can profitably subvert it).
- Gale–Shapley (deferred acceptance), : while some doctor is unmatched, that doctor proposes to the next hospital on its list; each hospital tentatively holds its best offer so far and rejects the rest. It looks greedy, but acceptances are revocable — a hospital can drop a tentatively-held doctor for a better proposal, which is what lets it escape bad early commitments.
- Always produces a stable matching. It is proposer-optimal (every doctor gets the best hospital it could have in any stable matching) and simultaneously receiver-worst — the proposing side has no incentive to misreport, the other side sometimes does.
- Rural-hospital theorem: a hospital left unfilled in one stable matching is matched to the exact same set of doctors in every stable matching.
Core competency set
- Define blocking pair and stable matching; give the Gale–Shapley procedure and why “deferred/revocable” matters.
- State proposer-optimal = receiver-worst and the strategy-proofness of the proposing side.
7. Memorize cold
- DFS/BFS . Topo sort = decreasing finish time (DAG). BFS layer = unweighted distance; bipartite ⇔ no odd cycle.
- SCC / Kosaraju: DFS finish times → reverse edges → DFS by decreasing finish time; trees = SCCs. SCC graph is a DAG.
- Dijkstra (greedy, nonneg) . Bellman–Ford (DP, neg edges) , edges, , detects neg cycles. Floyd–Warshall (APSP) , .
- DP recipe: optimal substructure → recurrence → bottom-up/memoize → reconstruct. 0/1 knapsack , . LCS or , .
- Greedy = exchange argument (“after choices an optimal solution still extends them”). Activity selection = earliest finish; job scheduling = ratio; Huffman = merge two least frequent.
- MST cut lemma: cheapest edge crossing a cut respecting is in some MST. Prim (grow one tree, ); Kruskal (sort, add acyclic, with union-find: Find→root, Union by size).
- Max-flow = min-cut. Ford–Fulkerson: augment along residual paths (forward + reverse for cancellation). Edmonds–Karp (BFS) . Bipartite matching = unit-capacity flow.
- Karger: contract random edges to 2 supernodes; ; repeat ; Karger–Stein . Monte Carlo.
- Gale–Shapley: propose / tentatively-hold / reject; stable, proposer-optimal = receiver-worst; .
Named moves (cross-track glossary): graph-in-disguise (model it as vertices + edges); finish-time-ordering (DFS → topo sort & SCC); BFS-layer-is-distance; greedy-finalize (Dijkstra/Prim extract the safe-closest); budget-the-output (Bellman–Ford: paths with edges); intermediate-set-recursion (Floyd–Warshall); optimal-substructure-then-memoize (DP, vs 13a’s disjoint divide & conquer); case-on-the-last-decision (knapsack/LCS recurrences); exchange-argument (greedy correctness); cut-lemma (the MST safe edge); union-find-for-cycles; residual-graph + augmenting-path + cancellation (flow); max-flow=min-cut (the duality, cf. LP duality track 10); matching-as-unit-flow; contract-random-edges (Karger, Monte Carlo — cf. 13a Las Vegas vs Monte Carlo); defer-acceptance (revocable greedy, Gale–Shapley).