Greedy

Pick the locally best step. Interval scheduling.

A greedy algorithm commits to the locally best choice and never backtracks. It is correct only for some problems. Interval scheduling (finish first) is the classic yes.

Goal

Schedule non-overlapping kiosk slots by earliest finish time.

Earliest finish

slots = [
    ("Ada", 9, 11),
    ("Bena", 10, 12),
    ("Caleb", 11, 13),
    ("Dina", 8, 9),
]
slots = sorted(slots, key=lambda s: s[2])
chosen = []
end = -1
for name, start, finish in slots:
    if start >= end:
        chosen.append(name)
        end = finish
print("order by finish", [s[0] for s in slots])
print("chosen", chosen)

Dina 8–9, Ada 9–11, Caleb 11–13. Bena overlaps Ada.

A greedy that can fail

# Coin change: greedy largest-first is wrong for some denoms.
def greedy_coins(amount, denoms):
    denoms = sorted(denoms, reverse=True)
    used = []
    for d in denoms:
        while amount >= d:
            amount -= d
            used.append(d)
    return used, amount

print(greedy_coins(30, [1, 10, 25]))
print(greedy_coins(30, [1, 5, 10, 20]))

For 1, 10, 25, greedy on 30 takes 25+… and may use more coins than 10+10+10. Next chapter’s DP finds the fewest coins.

Pitfall

“Greedy feels right” is not a proof. If you cannot state the invariant (here: the leftover interval stays schedulable), write DP or search instead.