A greedy algorithm builds a solution step by step by making the locally optimal choice at each step. It never reconsiders its decisions. When a problem has greedy choice property and optimal substructure, this simple strategy yields the globally optimal solution.
The greedy choice property states that a globally optimal solution can be arrived at by making a locally optimal (greedy) choice. In other words, you never need to reconsider a choice once it is made.
Optimal substructure means the optimal solution to the overall problem contains optimal solutions to its subproblems. This property is shared with Dynamic Programming, but greedy algorithms use it differently — they solve one subproblem after another without exploring alternatives.
Huffman coding is a greedy algorithm for lossless data compression. It builds a variable-length prefix-free code where more frequent characters get shorter codes and less frequent characters get longer codes.
import heapq
from collections import Counter
def huffman_encode(data):
freq = Counter(data)
heap = [[w, [char, ""]] for char, w in freq.items()]
heapq.heapify(heap)
while len(heap) > 1:
lo = heapq.heappop(heap)
hi = heapq.heappop(heap)
for pair in lo[1:]:
pair[1] = "0" + pair[1]
for pair in hi[1:]:
pair[1] = "1" + pair[1]
heapq.heappush(heap, [lo[0] + hi[0]] + lo[1:] + hi[1:])
return sorted(heap[0][1:], key=lambda p: len(p[1]))
# Usage: huffman_encode("abracadabra")
Given a set of activities with start and end times, select the maximum number of non-overlapping activities. The greedy strategy: always pick the activity with the earliest finish time.
def activity_selection(activities):
# activities: list of (start, end)
activities.sort(key=lambda x: x[1]) # sort by finish time
selected = [activities[0]]
last_end = activities[0][1]
for start, end in activities[1:]:
if start >= last_end:
selected.append((start, end))
last_end = end
return selected
Time complexity: O(n log n) due to sorting.
Unlike the 0/1 Knapsack (which requires DP), the Fractional Knapsack allows taking fractions of items. The greedy strategy is to take as much as possible of the item with the highest value-to-weight ratio.
def fractional_knapsack(items, capacity):
# items: list of (value, weight)
items.sort(key=lambda x: x[0] / x[1], reverse=True)
total_value = 0.0
for value, weight in items:
if capacity >= weight:
total_value += value
capacity -= weight
else:
total_value += value * (capacity / weight)
break
return total_value
Time complexity: O(n log n). This is the classic "take as much as you can of the most valuable thing."
The Traveling Salesman Problem (TSP) asks: given cities and distances, find the shortest route that visits each city exactly once and returns to the start. TSP is NP-hard, but a simple greedy approximation is the Nearest Neighbor heuristic: start at a city and always visit the nearest unvisited city.
def tsp_nearest_neighbor(graph, start=0):
n = len(graph)
visited = [False] * n
path = [start]
visited[start] = True
current = start
for _ in range(n - 1):
next_city = min(
(i for i in range(n) if not visited[i]),
key=lambda i: graph[current][i]
)
visited[next_city] = True
path.append(next_city)
current = next_city
return path
The nearest neighbor heuristic gives a solution within a factor of O(log n) of optimal in the worst case, but often performs much better in practice.
| Works (Greedy Choice Property holds) | Does Not Work (Use DP instead) |
|---|---|
| Huffman Coding | 0/1 Knapsack |
| Activity Selection | Longest Common Subsequence |
| Fractional Knapsack | Coin Change (some coin systems) |
| Dijkstra's Shortest Path | Edit Distance |
| Prim's / Kruskal's MST | Traveling Salesman (optimal) |
Implement a greedy algorithm to solve the Minimum Coins problem: given coin denominations [1, 5, 10, 25] and an amount N, use the greedy strategy (always take the largest possible coin) to return the minimum number of coins. Does this always give the optimal answer? Try with denominations [1, 3, 4] and amount 6 — the greedy answer is 3 (4+1+1), but the optimal is 2 (3+3).