Review of Princeton Algorithms II course on Coursera.
My solution to the homework
Course Overview
topic | data structures and algorithms |
---|---|
data types | stack, queue, bag, union-find, priority queue |
sorting | quicksort, mergesort, heapsort |
searching | BST, red-black BST, hash table |
graphs | BFS, DFS, Prim, Kruskal, Dijkstra |
strings | radix sorts, tries, KMP, regexps, data compression |
advanced | B-tree, suffix array, maxflow |
Undirected Graphs
Terminology:
- Path: sequence of vertices connected by edges
- Cycle: path whose first and last vertices are the same

Some graph problems:
- Path: Is there a path between
and - Shortest path: What is the shortest path between
and - Cycle: Is there a cycle in the graph
- Euler tour: Is there a cycle that uses each edge exactly once
- Hamilton tour: Is there a cycle that uses each vertex exactly once.
- Connectivity: Is there a way to connect all of the vertices?
- MST: What is the best way to connect all of the vertices?
- Biconnectivity: Is there a vertex whose removal disconnects the graph?
- Planarity: Can you draw the graph in the plane with no crossing edges
- Graph isomorphism: Do two adjacency lists represent the same graph?
API
|
|
For vertices, convert them between names and integers with symbol table
For edges, we have 3 choices:
- Maintain a list of the edges
- Maintain a 2-D
-by- boolean matrix - Maintain a vertex-indexed array of lists
Comparison:
Representation | Space | Add edge | Edge between v and w | Iterate over vertices adjacent to v |
---|---|---|---|---|
list of edges | E | 1 | E | E |
adjacency matrix | V2 | 1 | 1 | V |
adjacency lists | E + V | 1 | degree(v) | degree(v) |
In practice, use adjacency lists because
- Algorithms based on iterating over vertices adjacent to
- Real-world graphs tend to be sparse
|
|
Depth-First Search
- Mark vertex
as visited - Recursively visit all unmarked vertices adjacent to
|
|
After DFS, can find vertices connected to marked
) and can find a path to edgeTo
is a parent-link representation of a tree rooted at
Breadth-First Search
- Remove vertex
from queue - Add to queue all unmarked vertices adjacent to
and mark them
|
|
BFS examines vertices in increasing distance from
Connected Components
Definition:
- Vertices
and are connected if there is a path between them - A connected component is a maximal set of connected vertices
Use DFS to partition vertices into connected components, then can answer whether
|
|
Directed Graphs

API
Almost same as Graph
:
|
|
Digraph Search
Both DFS and BFS are digraph algorithms, code is identical to undirected graphs.
DFS search applications:
- Every program is a digraph. Vertex=basic block of instructions; edge=jump
- Dead-code elimination: find and move unreachable code
- Infinite-loop detection: whether exit is unreachable
- Every data structure is a digraph. Vertex=object; edge=reference
- Mark-sweep algorithm: collect unreachable objects as garbage
BFS search applications:
- Multiple-source shortest paths: initialize by enqueuing all source vertices
- Web crawler
Topological Sort
Precedence scheduling: Given a set of tasks to be completed with precedence constraints, in which order should we schedule the tasks?
- Represent data as digraph: vertex=task; edge=precedence constraint
- Represent the problem as topological sort: redraw directed acyclic graph (DAG) so all edges point upwards
Run DFS and return vertices in reverse postorder.
Strong Components
Definition:
- Vertices
and are strongly connected if there is both a directed path from to and a directed path from to - A Strong component is a maximal subset of strongly-connected vertices
Applications:
- Food Web. Vertex=species; edge=from producer to consumer; strong component=subset of species with common energy flow
- Software module dependency graph. Vertex=software module; edge=from module to dependency; strong component=subset of mutually interacting modules
Kosaraju-Sharir algorithm:
- Compute topological order (reverse postorder) in Reverse Graph
- Run DFS in
, visiting unmarked vertices in the order computed above
|
|
HW6 Wordnet
- WordNet digraph: build digraph from input files. WordNet.java
synsets.txt
saves all the id and corresponding words (one id can have one or more words, and one word can have one or more ids)hypernyms.txt
contains hypernym relationships
- Shortest ancestral path: given two vertices, find the directed paths to their common ancestor with the max total length. SAP.java
- Outcast detection: given a list of WordNet nouns, find the noun least related to the others. This can be measured by the sum of SAP distances to all the other vertices. Outcast.java
Minimum Spanning Trees
Definition: A spanning tree of
Problem: Given undirected graph
Edge-Weighted Graph API
Edge abstraction needed for weighted edges.
|
|
Edge-weighted graph representation:
|
|
Greedy Algorithm
Simplifications:
- Edge weights are distinct
- Graph is connected
Then MST exists and is unique.
Definitions:
- A cut in a graph is a partition of its vertices into two (nonempty) sets
- A crossing edge connects a vertex in one set with a vertex in the other.
Given any cut, the crossing edge of min weight is in the MST.
Algorithm:
- Start with all edges colored white
- Find cut with no black crossing edges; color its min-weight edge black
- Repeat until
edges are colored black
Remove simplifying assumptions:
- Greedy MST still correct if equal weights are present
- Compute MST of each component if graph is not connected
Efficient implementations: How to choose cut? How to find min-weight edge?
Kruskal’s Algorithm
- Consider edges in ascending order of weight
- Add next edge to tree
unless doing so would create a cycle
The challenge is how to examine adding edge
- Maintain a set for each connected component in
- If
and are in same set, then adding - would create a cycle - To add
- to , merge sets containing and
|
|
Prim’s Algorithm
- Start with vertex
and greedily grow tree - Add to
the min weight edge with exactly one endpoint in - Repeat until
edges
Lazy Implementation
Maintain a PQ of edges with (at least) one endpoint in
- Delete-min to determine next edge
to add to - Disregard if both endpoints
and are marked (both in ) - Otherwise, let
be the unmarked vertex (not in )- add to PQ any edge incident to
- add
to and mark
- add to PQ any edge incident to
|
|
Eager Implementation
Maintain a PQ of vertices connected by an edge to
- Delete min vertex
and add its associated edge - to - Update PQ by considering all edges
- incident to- ignore if
is already in - add
to PQ if not already in it - update priority of
if - becomes shortest edge connecting to
- ignore if
|
|
And we need a IndexMinPQ
data structure
|
|
Shortest Paths
Given an edge-weighted digraph, find the shortest path from
Algorithm | Restriction | Typical case | Worst case | Extra space |
---|---|---|---|---|
Topological sort | no directed cycles | E + V | E + V | V |
Dijkstra (binary heap) | no negative weights | E log V | E log V | V |
Bellman-Ford | no negative cycles | E V | E V | V |
Bellman-Ford (queue-based) | no negative cycles | E + V | E V | V |
API
Weighted directed edge API:
|
|
The Edge-weighted digraph API is the same as EdgeWeightedGraph
.
Single-source shortest paths API:
|
|
Edge Relaxation
Relax edge
distTo[v]
is length of shortest known path from todistTo[w]
is length of shortest known path from toedgeTo[w]
is last edge on shortest known path from to- If
gives shorter path to through , update bothdistTo[w]
andedgeTo[w]
Different ways to choose which edge to relax:
- Dijkstra’s algorithm (nonnegative weights)
- Topological sort algorithm (no directed cycles)
- Bellman-Ford algorithm (no negative cycles)
Dijkstra’s Algorithm
- Consider vertices in increasing order of distance from
- Add vertex to tree and relax all edges pointing from that vertex
|
|
Prim’s algorithm is essentially the same algorithm, distinct in the way to choose next vertex for the tree:
- Prim’s algorithm choose the closest vertex to the tree (via undirected edge)
- Dijkstra’s algorithm choose the closest vertex to the source (via a directed path)
Topological Sort
Suppose the edge-weighted digraph has no directed cycles
- Consider vertices in topological order
- Relax all edges pointing from that vertex
It is more efficient to get the SPT. Time is proportional to
|
|
Applications:
- Seam carving: Resize an image without distortion
- Grid DAG: vertex = pixel; edge = from pixel to 3 downward neighbors
- Weight of pixel = energy function of 8 neighboring pixels
- Seam = shortest path (sum of vertex weights) from top to bottom
- Find longest paths in edge-weighted DAGs
- Topological sort algorithm works with negative weights.
- Negate all the weights, find shortest path, negate weights in result.
- Parallel job scheduling: Given a set of jobs with durations and precedence constraints, schedule the jobs (by finding a start time for each) so as to achieve the minimum completion time.
- Source and sink vertices. Two vertices for each job (begin and end).
- Three edges for each job. Begin to end (weighted by duration); source to begin (0 weight); end to sink (0 weight)
- One edge for each precedence constraint (0 weight)
- Use longest path from the source to schedule each job
Bellman-Ford
Dijkstra doesn’t work with negative edge weights.
A SPT exists iff no negative cycles, A negative cycle is a directed cycle whose sum of edge weights is negative.
Bellman-Ford algorithm:
|
|
Dynamic programming algorithm can be used to compute SPT in any edge-weighted graph with no negative cycles in time proportional to
- If
distTo[v]
does not change during pass , no need to relax any edge pointing from in pass - Maintain a queue of vertices whose
distTo[]
changed
Negative cycle can be found by Bellman-Ford algorithm too: If any vertex
Negative cycle application: Arbitrage detection. Given table of exchange rates, is there an arbitrage opportunity?
- Vertex = currency; edge = transaction; weight = exchange rate
- Find a directed cycle whose product of edge weights > 1
- Take
for the weights so that multiplication > 1 turns to addition < 0 - Equivalent to find a negative cycle
HW7 Seam Carving
Seam Carving is a content-aware image resizing technique. Remove one row/column every time.
- Design energy function for pixels
- Find seam with minimal energy with one pixel every row/column, and every two adjacent seam points differ at most 1 column/row
- Delete seam
For this problem, dynamic programming is equivalent to treat the image as a graph and find the shortest path on it.
- Treat every pixel as a vertex (column, row) and there are edges connecting from it to vertexes (column-1, row+1), (column, row+1) and (column+1, row+1), unless the vertex is invalid.
- This graph is a DAG so the shortest path problem can be solved by topological sort. Moreover, since edges are connected layer by layer, we can sort it layer by layer too.
- To optimize the algorithm, energy can be precomputed and stored. When removing seams, only points near the removed location should be updated.
- Find/remove horizontal and vertical seam are equivalent, except for a transpose.
Solution: SeamCarver.java
Maximum Flow
Definition:
- A
-cut is a partition of the vertices into two disjoint sets, with in one set and is the other set - Its capacity is the sum of the capacities of the edges from
to - A
-flow is a an assignment of values to the edges such that:- Capacity constraints:
edge’s flow edge’s capacity - Local equilibrium: inflow = outflow at every vertex (except
and )
- Capacity constraints:
- The value of a flow is the inflow at
Mincut problem: Find a cut of minimum capacity
Maxflow problem: Find a flow of maximum value
Ford-Fulkerson Algorithm
For digraph with
- Start with
flow - Fina an undirected path from
to such that (Augmenting Path)- Can increase flow on forward edges (not full)
- Can decrease flow on backward edge (not empty)
- Increase flow on that path by bottleneck capacity
FF performance depends on choice of augmenting paths:
Augmenting path | Number of paths | Implementation |
---|---|---|
shortest path | queue (BFS) | |
fattest path | priority queue | |
random path | randomized queue | |
DFS path | stack (DFS) |
Maxflow-Mincut Theorem
Definition: The net flow across a cut
Flow-value lemma: Let
Maxflow-mincut theorem: Value of the maxflow = capacity of mincut
To compute mincut
Implementation
Residual network:
- Use forward edge to represent residual capacity
- Use backward edge to represent flow
Augmenting path in original network is equivalent to directed path in residual network

Flow Edge API:
|
|
Flow Network API:
|
|
Ford-Fulkerson algorithm:
|
|
Applications
Bipartite Matching Problem
Equivalent to: Given a bipartite graph, find a perfect matching.
- Create
, , one vertex for each student, and one vertex for each job - Add edge from
to each student (capacity ) - Add edge from each job to
(capacity ) - Add edge from student to each job offered (capacity infinity)
Every perfect matching in bipartite graph corresponds to a maxflow of value
Baseball Elimination Problem
Given each team’s current wins and losses, and their games left to play, determine whether team
Construct the graph:
- Create
, , one vertex for each pair of teams other than , and one vertex for each team other than - Add edge from
to each pair of teams (capacity = number of games left between this pair) - Add edge from each team
to (capacity = ) upper-bound on the games that can win - Add edge from each pair of teams to two corresponding teams (capacity infinity)
Team
HW8 Baseball Elimination
To check whether team
- Trivial elimination: The maximum number of games team
can win is less than the number of wins of some other team - Nontrivial elimination: Solve a maxflow problem as mentioned above
Solution: BaseballElimination.java
String Sorts
worst | average | extra space | stable? | |
---|---|---|---|---|
LSD | ✔ | |||
MSD | ✔ | |||
3-way string quicksort |
String vs. StringBuilder
Operations:
- Length: Number of characters
- Indexing: Get the
-th character - Substring extraction: Get a contiguous subsequence of characters
- String concatenation: Append one character to end of another string
length() | charAt() | substring() | concat() | |
---|---|---|---|---|
String | 1 | 1 | 1 | N |
StringBuilder | 1 | 1 | N | 1 |
String:
- sequence of characters (immutable)
- Immutable
char[]
array, offset, and length
StringBuilder:
- Sequence of characters (mutable)
- Resizing
char[]
array and length
Key-Indexed Counting
Compare-based algorithms require
We can do better if we don’t depend on key compares.
Key-indexed counting:
- Assumption: keys are integers between
and - Implication: can use key as an array index
Sort an array a[]
of
- Count frequencies of each letter using key as index
- Compute frequency cumulates which specify destinations
- Access cumulates using key as index to move items
- Copy back into original array
|
|
LSD Radix Sort
LSD (Least-Significant-Digit-first) string sort:
- Consider characters from right to left
- Stably sort using
-th character as the key (key-indexed counting)
|
|
MSD Radix Sort
MSD (Most-Significant-Digit-First) string sort:
- Partition array into
pieces according to first character - Recursively sort all strings that start with each character
Treat variable-length strings as if they had an extra char at end (smaller than any char)
|
|
|
|
MSD String Sort vs. Quicksort
Disadvantages of MSD string sort:
- Extra space for
aux[]
- Extra space for
count[]
- Inner loop has a lot of instructions
- Accesses memory “randomly” (cache inefficient)
Disadvantage of quicksort:
- Linearithmic number of string compares (not linear)
- Has to rescan many characters in keys with long prefix matches
3-Way Radix Quicksort
Do 3-way partitioning on the
- Less overhead than
-way partitioning in MSD string sort - Does not re-examine characters equal to the partitioning char
|
|
3-Way String Quicksort vs. Standard Quicksort
Standard quicksort:
- Uses
string compares on average - Costly for keys with long common prefixes (and this is a common case)
3-way string quicksort:
- Uses
character compares on average - Avoids re-comparing long common prefixes
Suffix Arrays
Problem: Given a text of
- suffix sort the text
- Binary search for query; scan until mismatch

Tries
String symbol table API:
|
|
Goal: Faster than hashing, more flexible than BSTs.

R-Way Tries
- Store characters in nodes (not keys)
- Each node has
children, one for each possible character
Search: Follow links corresponding to each character in the key
- Search hit: node where search ends has a non-null value
- Search miss: reach null link or node where search ends has null value
Insertion: Follow links corresponding to each character in the key
- Encounter a null link: create new node
- Encounter the last character of the key: set value in that node
Deletion:
- Find the node corresponding to the key and set value to null
- If node has null value and all null links, remove that node (and recur)
|
|
Performance:
- Search hit: Need to examine all
characters for quality - Search miss: Examine only a few characters for typical case
- Space:
null links at each leaf (sublinear if many short strings share common prefixes)
Ternary Search Tries
- Store characters and values in nodes (not keys)
- Each node has 3 children: smaller (left), equal (middle), larger (right)
Search: Follow links corresponding to each character in the key
- If less, take left link; if greater, take right link
- If equal, take the middle link and move to the next key character
Insertion: Follow links corresponding to each character in the key
- Encounter a null link: create new node
- Encounter the last character of the key: set value in that node
Deletion:
- Find the node corresponding to the key and set value to null
- If node has null value and all null links, remove that node (and recur)
|
|
TST vs. Hashing
Hashing:
- Need to examine entire key
- Search hits and misses cost about the same
- Performance relies on hash function
- Does not support ordered symbol table operations
TST:
- Works only for strings (or digital keys)
- Only examines just enough key characters
- Search miss may involve only a few characters
- Support ordered symbol table operations (plus others)
Substring Search
Find pattern of length
Brute force: Check for pattern starting at each text position.
- We want linear-time guarantee
- We want to avoid backup problem: treat input as stream of data

Knuth-Morris-Pratt
DFA
DFA (Deterministic finite state automaton):
- Finite number of states (including start and halt)
- Exactly one transition for each char in alphabet
- Accept if sequence of transitions leads to halt state
The DFA state after reading in text[i]
is the length of longest prefix of pattern[]
that is a suffix of text[0:i]
Constructing the DFA:
- Copy of
dfa[][X]
todfa[][j]
for mismatch case - Set
dfa[pat.charAt(j)][j]
toj+1
for match case - Update
X
|
|
KMP
Once we have the dfa
matrix, we can use this make transition without backup
|
|
Boyer-Moore
- Scan characters in pattern from right to left
- Can skip as many as
text chars when finding one not in the pattern
Precompute index of rightmost occurrence of character in pattern to decide how much to skip:
|
|
Boyer-Moore:
|
|
Rabin-Karp
Use modular hashing:
- Compute a hash of pattern characters
to - For each
, compute a hash of text characters to - if pattern hash = text substring hash, check for a match
Horner’s method: linear-time method to evaluate degree-
|
|
Rabin-Karp:
|
|
HW9 Boggle
Find valid words in a board by connecting characters to its eight neighbors.
- Add dictionary to 26-way Tries
- Use dfs to search the board. Early termination can be made when no prefix match
- dfs can be run with node expansion together to avoid starting from the root every time
Solution: BoggleSolver.java
Regular Expressions
- Substring search: Find a single string in text
- Pattern matching: Find one of a specified set of strings in text
Regular Expression Pattern:
operation | order | example RE | matches | dose not match |
---|---|---|---|---|
concatenation | 3 | AABAAB | AABAAB | every other string |
or | 4 | AA | BAAB | AA BAAB | every other string |
closure | 2 | AB*A | AA ABBBBBA | AB ABABA |
parentheses | 1 | A(A | B)AAB | AAAAB ABAAB | every other string |
parentheses | 1 | (AB)*A | A ABABABABA | AA ABBA |
wildcard | .U.U.U. | CUMULUS JUGULUM | SUCCUBUS TUMULTUOUS | |
character class | [A-Za-z][a-z]* | word Captitalized | camelCase 4illegal | |
at least 1 | A(BC)+DE | ABCDE ABCBCDE | ADE BCDE | |
exactly k | [0-9]{5}-[0-9]{4} | 08540-1321 19072-5541 | 111111111 166-54-111 |
NFA
Kleene’s theorem:
- For any DFA, there exists a RE that describes the same set of strings
- For any RE, there exists a DFA that recognized the same set of strings
The DFA built from RE may have exponential number of states, so instead we use NFA (Nondeterministic finite state automata).
Regular-expression-matching NFA:
- RE enclosed in parentheses
- One state per RE character (start=
, accept= ) - Red
-transition (change state, but don’t scan text) - Black match transition (change state and scan to next text char)
- Accept if any sequence of transitions end in accept state

The Nondeterminism comes from different transitions. The program has to consider all possible transition sequences.
Basic plan:
- Build NFA from RE
- Simulate NFA with text as input
NFA Simulation
NFA representation:
- State names: Integers from
to - Match-transitions: Keep regular expression in array
re[]
-transitions: Store in a digraph
Simulation Steps:
- Run DFS from each source, without unmarking vertices
- Maintain set of all possible states that NFA could be in after reading in the first
characters - When no more input characters, accept if any state in the set is an accept state
|
|
Time complexity: Determining whether an
NFA Construction
Construction process:
- States: Include a state for each symbol in the RE, plus an accept state.
- Concatenation: Add match-transition edge from state corresponding to characters in the alphabet to next state.
- Parentheses: Add
-transition edge from parentheses to next state. - Closure: Add three
-transition edges for each*
operator. - Or: Add two
-transition edges for each|
operator

|
|
Time complexity: Building the NFA corresponding to an
Data Compression
Lossless compression and expansion:
- Message: Binary data
we want to compress - Compress: Generates a “compressed” representation
- Expand: Reconstructs original bitstream
Static model: Same model for all texts
- Fast
- Not optimal: different texts have different statistical properties
- Ex: ASCII, Morse code
Dynamic model: Generate model bases on text
- Preliminary pass needed to generate model
- Must transmit the model
- Ex: Huffman code
Adaptive model: Progressively learn and update model as you read text
- More accurate modeling produces better compression
- Decoding must start from beginning
- Ex: LZW
Run-Length Encoding
Simple type of redundancy in a bitstream: Long runs of repeated bits
|
|
Representation: 4-bit counts to represent alternating runs of 0s and 1s (15 0s, 7 1s, 7 0s, 11 s)
|
|
Huffman Compression
Variable-length codes: Use different number of bits to encode different chars.
To avoid ambiguity, ensure that no codeword is a prefix of another.
Use binary trie to represent the prefix-free code.
Huffman trie node data type:
|
|
Expansion:
|
|
Read and write:
|
|

To find the best prefix-free code, use Huffman algorithm:
- Count freq for each char in input
- Start with one node for each char with weight equal to freq
- Select two tries with min weight and merge into single trie with cumulative weight
|
|
LZW Compression
- Create ST associating
-bit codewords with string keys - Initialize ST with codewords for single-char keys
- Find longest string
in ST that is a prefix of unscanned part of input - Write the
-bit codeword associated with - Add
to ST, where is next char in the input
Use a trie that supports longest prefix match to represent LZW compression code table:
|
|
HW10 Burrows–Wheeler
Implement the Burrows-Wheeler data compression algorithm
- Burrows–Wheeler transform. Given a typical English text file, transform it into a text file in which sequences of the same character occur near each other many times. BurrowsWheeler.java, CircularSuffixArray.java
- Move-to-front encoding. Given a text file in which sequences of the same character occur near each other many times, convert it into a text file in which certain characters appear much more frequently than others. MoveToFront.java
- Huffman compression. Given a text file in which certain characters appear much more frequently than others, compress it by encoding frequently occurring characters with short codewords and infrequently occurring characters with long codewords.