Greedy algorithms solve optimization problems by making the locally optimal choice at each step with the hope of finding a global optimum. The key principle is: make the best decision you can right now, without worrying about future consequences.
Greedy algorithms don’t always produce optimal solutions, but when they do, they’re often the most efficient approach.
Hopes that these local choices lead to a global optimum
function greedyApproach(problem) { let solution = []; while (!problem.isSolved()) { // Make the best choice right now const bestChoice = findLocalOptimum(problem); solution.push(bestChoice); problem.update(bestChoice); } return solution;}
Given a value v, if we want to make change for v using an infinite supply of coins with denominations [c1, c2, c3, ..., cn], what is the minimum number of coins needed? If it’s not possible to make change, return -1.Example:
Problem: Select maximum number of non-overlapping activities.Greedy Strategy: Always pick the activity that finishes earliest.
function activitySelection(activities: Array<[number, number]>) { // Sort by finish time activities.sort((a, b) => a[1] - b[1]); const selected = [activities[0]]; let lastFinish = activities[0][1]; for (let i = 1; i < activities.length; i++) { if (activities[i][0] >= lastFinish) { selected.push(activities[i]); lastFinish = activities[i][1]; } } return selected;}
Huffman Coding
Problem: Create optimal prefix-free binary codes for data compression.Greedy Strategy: Always combine the two nodes with smallest frequencies.
class HuffmanNode { constructor( public freq: number, public char?: string, public left?: HuffmanNode, public right?: HuffmanNode ) {}}function huffmanCoding(frequencies: Map<string, number>) { const heap = new MinHeap<HuffmanNode>( (a, b) => a.freq - b.freq ); // Add all characters to heap for (const [char, freq] of frequencies) { heap.insert(new HuffmanNode(freq, char)); } // Build tree while (heap.size() > 1) { const left = heap.extractMin(); const right = heap.extractMin(); const parent = new HuffmanNode( left.freq + right.freq, undefined, left, right ); heap.insert(parent); } return heap.extractMin();}
Dijkstra's Shortest Path
Problem: Find shortest path from source to all vertices.Greedy Strategy: Always expand the vertex with smallest distance from source.
function dijkstra(graph: number[][], source: number) { const n = graph.length; const dist = new Array(n).fill(Infinity); const visited = new Array(n).fill(false); dist[source] = 0; for (let i = 0; i < n - 1; i++) { // Find vertex with minimum distance let u = -1; for (let v = 0; v < n; v++) { if (!visited[v] && (u === -1 || dist[v] < dist[u])) { u = v; } } visited[u] = true; // Update distances for (let v = 0; v < n; v++) { if (graph[u][v] && !visited[v]) { dist[v] = Math.min( dist[v], dist[u] + graph[u][v] ); } } } return dist;}
Fractional Knapsack
Problem: Maximize value in knapsack when items can be divided.Greedy Strategy: Sort by value-to-weight ratio, take highest ratio first.
interface Item { value: number; weight: number;}function fractionalKnapsack( items: Item[], capacity: number): number { // Sort by value/weight ratio (descending) items.sort((a, b) => (b.value / b.weight) - (a.value / a.weight) ); let totalValue = 0; let remainingCapacity = capacity; for (const item of items) { if (remainingCapacity >= item.weight) { // Take whole item totalValue += item.value; remainingCapacity -= item.weight; } else { // Take fraction of item totalValue += (item.value / item.weight) * remainingCapacity; break; } } return totalValue;}
To prove a greedy algorithm is correct, typically use one of:
Greedy Stays Ahead
Exchange Argument
Show that after each step, the greedy solution is at least as good as any other solution.
Prove: greedy_solution[i] ≥ optimal_solution[i]for all steps i
Show that any optimal solution can be transformed into the greedy solution without making it worse.
1. Start with any optimal solution2. Exchange one element with greedy choice3. Show the solution is still optimal4. Repeat until solution matches greedy