cv.cm

Coding · Algorithms

Greedy algorithms explained: how they work, code and examples

5 min read

Short answer

A greedy algorithm builds a solution step by step and at each step takes the choice that looks best right now, without going back to change earlier choices. It is fast and simple, but it is only correct for problems with the greedy-choice property, where a locally best choice is always part of some optimal solution. Classic correct greedy algorithms include interval scheduling, Huffman coding, Dijkstra's algorithm and Kruskal's and Prim's minimum spanning tree algorithms.

What problem greedy algorithms solve

Greedy algorithms target optimization problems: choose the most activities, use the fewest coins, build the cheapest network, compress a file into the fewest bits. Instead of exploring every combination, a greedy algorithm sorts or prioritizes the options and commits to the best one at each step. That usually gives O(n log n) time, often just the cost of sorting.

The hard part is not the code but the proof. Many natural greedy rules are wrong, so you must either prove that the rule is correct or test it against a brute-force solution.

The idea in plain words

  1. Find a rule that ranks the choices, for example "earliest finishing time first" or "lightest edge first".
  2. Sort the options by that rule, or keep them in a priority queue.
  3. Go through the options and take each one that is still allowed by the constraints.
  4. Never undo a choice.

A greedy algorithm is correct when two properties hold. Greedy-choice property: some optimal solution contains the first greedy choice. Optimal substructure: after making that choice, the rest of the problem is a smaller instance of the same problem. The usual proof is an exchange argument: take any optimal solution and show that swapping one of its choices for the greedy choice does not make it worse.

Worked example: interval scheduling

You have one meeting room and these requests (start, end): A (1, 4), B (3, 5), C (0, 6), D (5, 7), E (3, 9), F (5, 9), G (6, 10), H (8, 11). Book as many non-overlapping meetings as possible. The correct greedy rule is: always pick the meeting that ends first among those that start after the last booked one ends.

Meeting (sorted by end)Start, endStarts after last end?DecisionLast end
A1, 4yes (nothing booked)Book4
B3, 5no (3 < 4)Skip4
C0, 6noSkip4
D5, 7yesBook7
E3, 9noSkip7
F5, 9noSkip7
G6, 10noSkip7
H8, 11yesBook11

Result: A, D, H, three meetings, which is the maximum. Why it works: the meeting that ends first leaves the most time for everything else, so replacing the first meeting of any optimal schedule with it never causes a conflict.

Python implementation

def max_meetings(meetings):
    """meetings: list of (name, start, end). Returns the booked names."""
    booked, last_end = [], float("-inf")
    for name, start, end in sorted(meetings, key=lambda m: m[2]):
        if start >= last_end:
            booked.append(name)
            last_end = end
    return booked

requests = [("A", 1, 4), ("B", 3, 5), ("C", 0, 6), ("D", 5, 7),
            ("E", 3, 9), ("F", 5, 9), ("G", 6, 10), ("H", 8, 11)]
print(max_meetings(requests))  # ['A', 'D', 'H']

A second classic example is the fractional knapsack: when you may take part of an item, taking items in order of value per unit of weight is optimal.

def fractional_knapsack(items, capacity):
    """items: list of (value, weight). Returns the best total value."""
    total = 0.0
    for value, weight in sorted(items, key=lambda i: i[0] / i[1], reverse=True):
        take = min(weight, capacity)
        total += value * take / weight
        capacity -= take
        if capacity == 0:
            break
    return total

print(fractional_knapsack([(60, 10), (100, 20), (120, 30)], 50))  # 240.0

When greedy fails: counterexamples

ProblemTempting greedy ruleCounterexampleCorrect method
Coin change, coins 1, 3, 4Take the largest coin firstAmount 6: greedy 4 + 1 + 1 (3 coins), optimal 3 + 3 (2 coins)Dynamic programming
0/1 knapsack (whole items only)Best value per weight firstCapacity 50, items (60, 10), (100, 20), (120, 30): greedy takes 60 + 100 = 160, optimal 100 + 120 = 220Dynamic programming
Interval schedulingShortest meeting first(0, 5), (4, 7), (6, 11): greedy takes only (4, 7), optimal takes the other twoEarliest end first
Shortest path with negative edgesDijkstra's closest node firstAny graph where a negative edge makes a finalized node cheaperBellman-Ford

Time and space complexity

AlgorithmTimeReason
Interval schedulingO(n log n)Sorting by end time; the scan is O(n).
Fractional knapsackO(n log n)Sorting by value per weight.
Huffman codingO(n log n)n − 1 merges, each with heap operations.
Kruskal's MSTO(E log E)Sorting edges plus near-constant union-find.
Dijkstra's shortest pathsO((V + E) log V)Heap-based priority queue.

Space is usually O(n) for the sorted copy or the heap. If the input is already sorted, many greedy algorithms run in O(n).

When to use greedy and real-world uses

  • Use greedy when you can prove the greedy-choice property, or when a fast approximate answer is acceptable. Otherwise use dynamic programming or backtracking.
  • Compression: Huffman coding builds optimal prefix codes by repeatedly merging the two least frequent symbols; it is used inside formats such as DEFLATE (ZIP, gzip, PNG).
  • Networks: minimum spanning trees (Kruskal, Prim) and shortest path routing (Dijkstra).
  • Scheduling: earliest deadline first, and booking rooms or machines.
  • Approximation: for set cover, the greedy rule "pick the set covering the most uncovered items" is not optimal but is guaranteed to be within a logarithmic factor of the optimum.
  • Interview problems: jump game, gas station, assign cookies, non-overlapping intervals, minimum number of arrows to burst balloons, task scheduler.

FAQ

What is a greedy algorithm in simple words?

It is an algorithm that always takes the option that looks best at the moment and never reconsiders. It is correct only for problems where these local choices always lead to a globally best answer.

How do I prove that a greedy algorithm is correct?

The usual method is an exchange argument: start from any optimal solution and show that you can replace its first choice with the greedy choice without making it worse, then repeat on the remaining problem.

Greedy or dynamic programming?

Try greedy first if a simple rule seems right, then look for a counterexample. If one exists, the problem needs dynamic programming, which compares all choices for each subproblem.

Is Dijkstra's algorithm greedy?

Yes. It always finalizes the closest unfinished node. This greedy choice is correct because edge weights are non-negative.

All coding tutorials