Data Structures & Algorithms
DSA IV: pattern matching, graphs, and dynamic programming.
Preprocessing a pattern turns a blind rescan into a series of informed skips, and the graph and dynamic programming algorithms both win by reusing work a plain recursion would throw away. These notes turn my Georgia Tech DSA IV material into demos you can step through.
A Module 0 refresher on Java generics under type erasure, reference semantics, Comparable/Comparator, and Big-O as the tightest reasonable upper bound.
Pattern matching: brute-force alignments, Boyer-Moore's last occurrence table, KMP's failure table, and Rabin-Karp's rolling hash.
Graph algorithms: adjacency list, matrix, and edge list representations, DFS and BFS in O(|V| + |E|), and Dijkstra's shortest paths on non-negative weights.
Minimum spanning trees: Prim's on the cut property, Kruskal's on the cycle property, the greedy paradigm and where it fails, and disjoint sets with path compression and union by rank.
Dynamic programming: memoization and optimal substructure, the O(nm) LCS table with backtracking, 0-1 knapsack, and the Bellman-Ford and Floyd-Warshall shortest-path algorithms.
#
concept notes
Three matchers, two strategies
Boyer-Moore and KMP both preprocess the pattern so a mismatch can shift it intelligently; Rabin-Karp instead screens each window with a rolling hash and only compares characters when the hashes agree. Boyer-Moore is sublinear in the typical case at O(m + n/m) but degrades to O(mn), KMP is O(m + n) whatever the input, and Rabin-Karp is linear with a good hash and O(mn) if every hash collides. Large alphabets suit Boyer-Moore, small alphabets or streaming text suit KMP, and searching many patterns at once suits Rabin-Karp.
Module 12 · Pattern Matching
Leaving the priority queue is the proof
Dijkstra splits vertices into unexplored, frontier (in the priority queue, possibly on a suboptimal path), and visited. A vertex is certified the instant it is dequeued, because a cheaper route would have gone through an already-visited neighbor and been enqueued smaller. A negative edge breaks exactly that certificate, since Dijkstra never revisits, which is why negative weights need Bellman-Ford (O(|V|·|E|)) or Floyd-Warshall (O(|V|³)).
Module 13 · Graph Algorithms
The cut property
Prim's is greedy: the visited set splits the graph into a cut, the priority queue holds the frontier edges crossing it, and dequeuing the smallest is the locally optimal choice. That choice is also globally correct, because any MST must contain the minimum-cost edge over any cut. The skeleton is Dijkstra's, but the priority pushed is the edge weight alone rather than a cumulative distance, and it runs in O(|E| log |E|) with a binary heap.
Module 14 · MST
Clusters, not a visited set
Kruskal's takes edges in weight order and keeps any that does not close a cycle, which the cycle property justifies: given distinct edge weights, the heaviest edge of a cycle belongs to no MST. Its clusters grow globally instead of outward from one source, so every vertex can be visited while the MST is still incomplete, and a visited set cannot tell a cluster merge from a cycle. A disjoint set answers that with find and union at amortized O(α(n)), so the heap dominates at O(|E| log |E|), or O(|E| log |V|) on a simple graph.
Module 14 · MST
Memoize the overlap
Dynamic programming is divide-and-conquer for problems whose subproblems repeat: compute each subproblem once, store it, and reuse it. Naive recursive Fibonacci is exponential, while the memoized version is O(n) time and O(n) space — the usual trade of polynomial space for polynomial time. Drawing subproblems as vertices and dependencies as edges shows the boundary: DP applies when that graph is a DAG, with top-down DP as a DFS over it and bottom-up as reverse topological order.
Module 15 · Dynamic Programming
Shortest paths, done bottom-up
Bellman-Ford relaxes every edge |V|−1 times, because a shortest path uses at most |V|−1 edges, and it handles the negative weights Dijkstra's cannot, in O(|V|·|E|) time and O(|V|) space. Floyd-Warshall solves all pairs in O(|V|³) time and O(|V|²) space by admitting one more intermediate vertex per outer iteration, which is why that loop has to be outermost. Both detect negative cycles: one extra Bellman-Ford round that still improves a distance, or a negative entry on the Floyd-Warshall diagonal.
Module 15 · Dynamic Programming