Greedy algorithms
Build a solution one locally best choice at a time, learn when that is provably optimal (interval scheduling, coins, fractional knapsack) and when it fails, and how to argue that a greedy rule is correct.
Scheduling: which rule is right?
var bookings = new List<(string Id, int Start, int End)>{ ("A", 6, 9), ("B", 9, 12), ("C", 8, 10), ("D", 12, 15), ("E", 15, 18), ("F", 14, 16), ("G", 5, 18), ("H", 10, 11)}; var chosen = new List<string>();int freeFrom = int.MinValue;foreach (var b in bookings.OrderBy(b => b.End)) // earliest finish first{ if (b.Start < freeFrom) continue; // clashes with the last one taken chosen.Add(b.Id); freeFrom = b.End;}Console.WriteLine($"{chosen.Count}: {string.Join(", ", chosen)}");4: A, H, D, E
Making change
Console.WriteLine(string.Join(" + ", GreedyChange(48, new[] { 20, 10, 5, 2, 1 })));Console.WriteLine(string.Join(" + ", GreedyChange(6, new[] { 4, 3, 1 }))); // 3 + 3 is better static List<int> GreedyChange(int amount, int[] coinsLargestFirst){ var used = new List<int>(); foreach (int c in coinsLargestFirst) while (amount >= c) { used.Add(c); amount -= c; } return used;}20 + 20 + 5 + 2 + 1 4 + 1 + 1
Fractional knapsack
var items = new[]{ (Name: "Steel", Value: 60, Weight: 10), (Name: "Copper", Value: 100, Weight: 20), (Name: "Timber", Value: 120, Weight: 30)}; double capacity = 50, total = 0;foreach (var it in items.OrderByDescending(i => (double)i.Value / i.Weight)) // best value per kg first{ double take = Math.Min(it.Weight, capacity); total += take * it.Value / it.Weight; capacity -= take; Console.WriteLine($"{it.Name}: {take} of {it.Weight} kg"); if (capacity == 0) break;}Console.WriteLine($"Value: {total}");Steel: 10 of 10 kg Copper: 20 of 20 kg Timber: 20 of 30 kg Value: 240
How to approach a greedy idea
State the greedy rule precisely (“sort by end time, take whatever fits”). Hunt for a counterexample with small inputs, as the widget does for earliest start and shortest first. Most wrong greedy rules fail on 3 or 4 items. If you cannot break it, argue it: an exchange argument (“swapping an optimal choice for the greedy one never makes things worse”) is the usual proof. If it does break, think about dynamic programming.
| Problem | Greedy rule | Optimal? |
|---|---|---|
limit. Find the fewest trips for all loads. { 3, 2, 2, 1 } with limit 3 → 3 trips; { 3, 5, 3, 4 } with limit 5 → 4 trips. Use a greedy rule.
Show a hint
Show the solution
Console.WriteLine(MinTrips(new[] { 3, 2, 2, 1 }, 3));Console.WriteLine(MinTrips(new[] { 3, 5, 3, 4 }, 5)); static int MinTrips(int[] loads, int limit){ var w = loads.OrderBy(x => x).ToArray(); int light = 0, heavy = w.Length - 1, trips = 0; while (light <= heavy) { if (w[light] + w[heavy] <= limit) light++; // the lightest rides along heavy--; // the heaviest always goes now trips++; } return trips;}3 4
Remember
Greedy algorithms take the locally best choice and never undo it: simple and fast. They are only correct for some problems; test small counterexamples first. Prove a greedy rule with an exchange argument: the greedy choice is never worse than an optimal one. Earliest finish, standard coins, fractional knapsack, MST and Dijkstra are greedy successes; general coin sets and 0/1 knapsack need DP.