Why This Matters
Greedy algorithms are one of the most elegant problem-solving paradigms in algorithm design. The core principle is deceptively simple: make the locally optimal choice at each step, aiming for a globally optimal solution. But what you're really being tested on is understanding when greedy works and why it fails. Exam questions probe whether you can identify the two conditions that guarantee optimality: optimal substructure (an optimal solution contains optimal solutions to subproblems) and the greedy choice property (a locally optimal choice can lead to a globally optimal solution).
These algorithms appear constantly in technical interviews and build the foundation for understanding dynamic programming. When you study these examples, don't just memorize the steps. Ask yourself: "What's the greedy choice? Why does it work here? Would it work with different constraints?"
Graph Optimization Algorithms
These algorithms solve fundamental graph problems by greedily selecting the "cheapest" option at each step. The key insight is that local minimum-cost decisions accumulate into globally optimal solutions when edge weights are non-negative.
Dijkstra's Shortest Path Algorithm
- Greedy choice: always expand the unvisited node with the smallest known distance. Once you expand a node, you've guaranteed the shortest path to it, so you never need to revisit it.
- A priority queue (min-heap) enables efficient selection of the next node, achieving O((V+E)logV) time complexity.
- Non-negative weights are required. Negative edges break the algorithm because a path that currently looks longer could become shorter after traversing a negative edge. For graphs with negative edges (but no negative cycles), use Bellman-Ford instead.
Prim's Minimum Spanning Tree Algorithm
- Greedy choice: add the minimum-weight edge connecting the growing tree to a non-tree vertex. The MST grows one vertex at a time from a starting node.
- A priority queue stores candidate edges, updating keys as new vertices join the tree.
- Performs well on dense graphs (where E approaches V2) because its vertex-centric approach avoids sorting all edges upfront.
Kruskal's Minimum Spanning Tree Algorithm
- Greedy choice: sort all edges by weight and add the smallest edge that doesn't create a cycle. This builds the MST edge by edge, considering edges globally rather than growing from one vertex.
- The Union-Find (Disjoint Set Union) data structure enables near-constant-time cycle detection. With path compression and union by rank, each operation runs in O(ฮฑ(n)), where ฮฑ is the inverse Ackermann function (effectively constant).
- Better suited for sparse graphs. The O(ElogE) sorting step dominates, so when E is much smaller than V2, Kruskal's is efficient.
Compare: Prim's vs. Kruskal's โ both produce a minimum spanning tree, but Prim's grows from a single vertex (vertex-centric) while Kruskal's considers all edges globally (edge-centric). If asked to choose between them, consider graph density: Prim's for dense graphs, Kruskal's for sparse.
Scheduling and Selection Problems
These problems involve choosing a maximum-size or maximum-value subset from competing options. The greedy insight is that finishing early leaves the most room for future selections.
Activity Selection Problem
- Greedy choice: always select the activity that finishes earliest. This maximizes the remaining time available for additional activities.
- Sorting by finish time is essential. Sorting by start time or by duration does not yield optimal results. For example, a short activity in the middle of the timeline could block two non-overlapping activities.
- This problem has a classic proof of greedy correctness: you can show that swapping any selected activity for one that finishes earlier never reduces the total number of activities selected.
Interval Scheduling
- Greedy choice: select the interval with the earliest end time that doesn't overlap with previous selections. The logic is identical to activity selection.
- This is a foundational problem that generalizes to weighted interval scheduling, which requires dynamic programming because you can no longer just count intervals โ you need to maximize total weight.
- Time complexity is O(nlogn), dominated by the initial sort. The selection pass itself is linear.
Job Sequencing Problem
- Greedy choice: sort jobs by profit in descending order, then schedule each job in the latest available slot before its deadline. Scheduling late preserves earlier slots for other jobs.
- Feasibility tracking requires checking slot availability. A simple approach uses an array of time slots; a more optimized version uses union-find.
- Deadline constraints make this different from pure interval scheduling. Here you're maximizing total profit, not the count of scheduled items.
Compare: Activity Selection vs. Job Sequencing โ both involve scheduling, but activity selection maximizes count (sort by finish time) while job sequencing maximizes profit (sort by profit descending). Know which sorting criterion matches which objective.
Knapsack and Resource Allocation
These problems optimize value subject to capacity constraints. The critical distinction is whether items are divisible โ this determines if greedy works.
Fractional Knapsack Problem
- Greedy choice: select items by highest value-to-weight ratio (wiโviโโ) until capacity is exhausted. If the last item doesn't fully fit, take a fraction of it.
- Divisibility is the key. Because items can be split, taking the "densest" value first always improves the solution. There's no scenario where skipping a high-ratio item and taking a lower-ratio one yields more total value.
- Guaranteed optimal, unlike the 0/1 knapsack. In 0/1 knapsack, items are indivisible, so a high-ratio item might waste capacity that two smaller items could fill more profitably. That version requires dynamic programming.
Coin Change Problem
- Greedy choice: always select the largest denomination that doesn't exceed the remaining amount. This is intuitive but unreliable.
- Works only for canonical coin systems like U.S. currency (1, 5, 10, 25). For the system {1, 3, 4}, greedy picks 4 + 1 + 1 = three coins to make 6, but the optimal answer is 3 + 3 = two coins.
- This is a classic counterexample for greedy limitations. For arbitrary denominations, dynamic programming is required to guarantee the minimum number of coins.
Compare: Fractional Knapsack vs. Coin Change โ both seem like "take the best ratio" problems, but fractional knapsack always works (items are divisible) while coin change sometimes fails (coins are discrete and indivisible). This distinction is a favorite exam topic.
Data Compression
Huffman coding demonstrates greedy algorithm design applied to information theory. The insight is that frequent symbols should have shorter codes, and building the tree bottom-up ensures optimality.
Huffman Coding
The algorithm builds an optimal prefix-free binary tree:
- Create a leaf node for each symbol, weighted by its frequency.
- Insert all nodes into a min-heap (priority queue).
- Extract the two nodes with the lowest frequency.
- Create a new internal node with these two as children, with frequency equal to their sum.
- Insert the new node back into the heap.
- Repeat steps 3-5 until only one node remains (the root).
- Greedy choice: repeatedly merge the two lowest-frequency nodes. This ensures the least frequent symbols end up deepest in the tree (longest codes) and the most frequent symbols stay near the root (shortest codes).
- Prefix-free codes mean no codeword is a prefix of another, so decoding is unambiguous without delimiters.
- Provably optimal for symbol-by-symbol encoding. It produces the minimum expected code length for a given set of frequencies.
Huffman Decoding
- Tree traversal: start at the root, follow '0' for left child and '1' for right child until you reach a leaf node, which gives you the decoded character. Then return to the root for the next character.
- The prefix property guarantees unique decoding. Each bit sequence maps to exactly one character.
- Linear-time decoding in the length of the encoded message (O(m) for m bits). The tree structure enables streaming decode with no need to buffer.
Compare: Huffman Coding vs. Decoding โ encoding requires building the tree (O(nlogn) with a heap for n symbols), while decoding just traverses it (O(m) for m bits). Both rely on the same tree, but they're distinct algorithmic processes.
Quick Reference Table
|
| Graph shortest paths | Dijkstra's Algorithm |
| Minimum spanning trees | Prim's, Kruskal's |
| Interval/activity scheduling | Activity Selection, Interval Scheduling |
| Profit maximization with deadlines | Job Sequencing |
| Value-to-weight optimization | Fractional Knapsack |
| Greedy failure cases | Coin Change (non-canonical systems), 0/1 Knapsack |
| Data compression | Huffman Coding, Huffman Decoding |
| Union-Find applications | Kruskal's Algorithm |
Self-Check Questions
-
Both Prim's and Kruskal's algorithms produce minimum spanning trees. What data structure does each rely on, and how does graph density affect which you should choose?
-
Why does the greedy approach guarantee an optimal solution for the Fractional Knapsack Problem but fail for the 0/1 Knapsack Problem?
-
Activity Selection and Job Sequencing both involve scheduling, but they sort by different criteria. What does each sort by, and why does this difference matter for optimality?
-
Compare Dijkstra's Algorithm and the greedy Coin Change approach: both make "minimum cost" choices, but one always works and one sometimes fails. What's the key difference?
-
If you're given character frequencies and asked to build a Huffman tree, walk through the greedy choice at each step. Why does merging the two smallest frequencies first lead to an optimal prefix-free code?