SEN2212 · Week 12 · Analyzing and implementing greedy algorithms.

Greedy algorithms: activity selection, fractional knapsack, coin change

The greedy idea, when it gives the correct result and when it breaks. Activity selection, fractional and 0/1 knapsack, and coin change examples with their counterexamples.

greedy algorithmgreedy-choice propertyoptimal substructureactivity selectioninterval schedulingfractional knapsack0/1 knapsackvalue-to-weight ratiocoin changecounterexampledynamic programming

A greedy algorithm makes the choice that looks best right now at each step and never questions that choice again. No going back, no trying all possibilities. That is why it is fast and its code is short. The problem: it does not give the right answer for every problem. Two kinds of questions come in the exam. One is “run the greedy, write the result” (an activity list, a knapsack value, coins). The other is “this greedy rule is wrong, give a counterexample”.

Golden rules:

  1. Greedy = the locally best choice, no going back.
  2. To be correct it needs two properties: the greedy-choice property and optimal substructure.
  3. To show it is wrong, a single counterexample is enough. Build a small example with 2-3 elements.
  4. In activity selection the criterion is the finish time, not the start or the duration.
  5. On something divisible (fractional) greedy works; on indivisible 0/1 it does not.

When is greedy correct?

If both properties hold together, greedy gives the optimum:

  • Greedy-choice property: There is always an optimal solution that contains the first choice that looks best. That is, making the greedy choice in the first step does not take you away from the optimum.
  • Optimal substructure: After the first choice, what remains is a smaller problem of the same kind, and its optimal solution, combined with the first choice, gives the optimum of the whole.

The standard way to prove this is the exchange argument: take any optimal solution, replace its first element with the greedy choice, and show that the solution is still valid and equally good.

The difference from dynamic programming (DP): DP tries all options of each subproblem and writes them to a table. Greedy picks only one. DP is safer, greedy is faster.

Activity selection

There is one hall, and each activity has a start and a finish time. Select the largest number of non-overlapping activities. An activity can start at the moment the previous one ends.

The greedy rule: Sort the activities by finish time. Take the one that finishes earliest. Then take the first activity whose start is not before the finish of the last selected one. Continue like this.

Example: a(1,3), b(2,5), c(4,6), d(6,8), e(5,9), f(7,10), g(9,11), h(10,12), i(11,13).

time   0123456789012345
a  1-3  ==
b  2-5   ===
c  4-6     ==
d  6-8       ==
e  5-9      ====
f  7-10       ===
g  9-11         ==
h 10-12          ==
i 11-13           ==
activity  start   lastFinish  decision
a(1,3)    1 >= -∞     -∞      TAKE, last=3
b(2,5)    2 <  3      3       skip
c(4,6)    4 >= 3      3       TAKE, last=6
d(6,8)    6 >= 6      6       TAKE, last=8
e(5,9)    5 <  8      8       skip
f(7,10)   7 <  8      8       skip
g(9,11)   9 >= 8      8       TAKE, last=11
h(10,12)  10 < 11     11      skip
i(11,13)  11 >= 11    11      TAKE, last=13
earliest finish: [a(1,3), c(4,6), d(6,8), g(9,11), i(11,13)]
optimum count (brute force): 5

Why is it correct? The activity that finishes earliest frees the hall earliest. If you replace the first activity of an optimal solution with it, no conflict arises (it finishes earlier or at the same time). What remains is the subproblem “those that start after the last finish”.

Wrong rules and their counterexamples

RuleCounterexampleResult of the ruleOptimum
Earliest startp(0,10), q(1,4), r(5,8)p (1 activity)q, r (2)
Shortest durationx(0,5), y(4,7), z(6,11)y (1 activity)x, z (2)
earliest start picks [p] vs finish [q(1,4), r(5,8)]
shortest picks [y] vs finish [x(0,5), z(6,11)]

PITFALL · the “boundary” condition. If the question says “an activity may start when the previous one finishes”, the comparison is >=; if it does not say so, >. That is why d(6,8) could be taken after c(4,6).

The knapsack problem

There is a knapsack with capacity W, and items each with a weight (wiw_i) and a value (viv_i). Make the total value as large as possible.

Example items: A(6 kg, 30), B(10 kg, 40), C(4 kg, 28), D(8 kg, 24). Capacity 15.

Itemwvv/w
C4287
A6305
B10404
D8243

Fractional: greedy is optimal

If you can split the item (like flour or gold dust), start with the one that has the highest value per kilo, take as much as fits, and split the last item.

  take 4/4 of C (ratio 7.0) total=28.0
  take 6/6 of A (ratio 5.0) total=58.0
  take 5/10 of B (ratio 4.0) total=78.0
fractional value 78.0

Why is it correct? Every kilo in the knapsack should be filled with the most valuable kilo. Replacing a lower-ratio kilo with a higher-ratio kilo always increases the value.

0/1: greedy breaks

If you cannot split the item, the same rule gives a wrong result:

  greedy 0/1 picks C A value 58
  optimal 0/1 picks B C value 68

After taking C and A, 5 kg is empty but B and D do not fit. Part of the capacity was wasted. B + C, on the other hand, makes 68 with 14 kg. The 0/1 knapsack is solved with DP in O(nW)O(nW) time (we do not go into DP in this course; it is enough to know why greedy breaks).

PITFALL · do not compute the ratio with int division. If v[i] / w[i] are two ints, values like 7/2 are truncated and the order breaks. Write (double) v[i] / w[i].

Coin change

Pay an amount with the fewest coins. The greedy rule: at each step give the largest coin that fits in the amount.

Coins {25, 10, 5, 1}, amount 68:

left   given
68     25
43     25
18     10
 8      5
 3      1
 2      1
 1      1
 0      → 7 coins

In “standard” systems like this (25-10-5-1, or 50-20-10-5-1) greedy is optimal. But not in every system:

{1,4,6} 8 greedy [6, 1, 1] dp 2
{1,7,10} 14 greedy [10, 1, 1, 1, 1] dp 2

For 8, 4 + 4 is two coins; because greedy grabbed 6, it had to pay the remaining 2 with 1s. For 14, it gave 10 + 1 + 1 + 1 + 1 instead of 7 + 7. The general solution is DP:

dp[0]=0,dp[a]=min⁡c≤a(dp[a−c]+1)dp[0] = 0,\qquad dp[a] = \min_{c \le a} \big(dp[a-c] + 1\big)

How to solve a greedy question in the exam

Three patterns for three kinds of questions:

"Run the greedy":
  write the criterion (finish, ratio, largest coin)
  sort, decide one by one with a table
"Is greedy optimal?":
  look for a counterexample with 2-3 elements
  if you cannot find one, build an exchange argument
"Write the method":
  sort the indices by the criterion
  a single loop, take if the condition holds

When looking for a counterexample, ask this question: “Can the first choice of greedy block two better choices at once?” In activity selection, a long activity that starts early; in coin change, a large coin that leaves an awkward remainder; in the 0/1 knapsack, a small item with a high ratio that leaves empty space plays this role. In the fractional knapsack and in the “earliest finishing” rule, such a blocker cannot be built; that is why they are correct.

Where are the greedy algorithms in this course?

AlgorithmGreedy choiceWeek
Dijkstrafinalize the node with the smallest tentative distance11
Huffmanmerge the two smallest frequencies12
Primadd the node that connects to the tree most cheaply13
Kruskaladd the cheapest edge that does not form a cycle13
Activity selectiontake the earliest finishing12

The correctness of all of them depends on proving the two properties above for that problem. Dijkstra breaking on a negative edge is also a violation of the greedy-choice property.

Common mistakes

Wrong ideaThe truth
Greedy is correct in every optimization problemOnly if the two properties hold; otherwise a counterexample can be found
In activity selection take the earliest startingTake the one that FINISHES earliest
In activity selection take the shortestCounterexample: x(0,5), y(4,7), z(6,11)
The ratio greedy in the 0/1 knapsack is optimalOnly in the fractional version; DP for 0/1
In the fractional knapsack start with the most valuable itemThe value/weight ratio, not the value
Greedy coin change is optimal in every coin system{1,4,6} and 8: greedy 3, optimum 2
Sort the s and f arrays separatelyThe pairs get broken; sort the indices by finish

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1medium

Activities (start, finish): a(1,3), b(2,5), c(4,6), d(6,8), e(5,9), f(7,10), g(9,11), h(10,12), i(11,13). An activity may start exactly when the previous one finishes. Using the earliest-finish-time greedy rule, which activities are selected?

Show answer
a, c, d, g, i (5 activities)

Sort by finish (already sorted). Last finish = -∞.
Take a(1,3), last=3. b starts at 2 < 3, skip. Take c(4,6), last=6. d(6,8): 6 ≥ 6, take, last=8. e 5 < 8, f 7 < 8, skip. Take g(9,11), last=11. h 10 < 11, skip. Take i(11,13).
Brute force also finds at most 5 activities; greedy is optimal.

Question 2easy

Which greedy rule always gives a maximum-size set of compatible activities?

  1. Earliest start time first
  2. Shortest duration first
  3. Earliest finish time first
  4. Fewest conflicts first
Show answer
C

The activity that finishes earliest is the choice that frees the most remaining time. The exchange argument: if you replace the first activity of any optimal solution with the earliest-finishing one, no conflict arises. All the other rules have counterexamples.

Question 3medium

Give the result of the earliest-start-time rule on p(0,10), q(1,4), r(5,8). What is the optimum?

Show answer
Earliest start picks only p (1 activity); optimum is q, r (2)

p starts earliest (0) but lasts until 10 and conflicts with both q and r. Greedy by finish takes q(1,4) then r(5,8): 2 activities. A single counterexample is enough to prove a rule wrong.

Question 4medium

Show that shortest-duration-first fails on x(0,5), y(4,7), z(6,11).

Show answer
Shortest picks only y (length 3); optimum is x, z (2)

The lengths are x=5, y=3, z=5. The shortest, y, is chosen; y conflicts with both x (4 < 5) and z (6 < 7), and nothing else can be added. Greedy by finish takes x(0,5) and z(6,11).

Question 5easy

What is the running time of the earliest-finish greedy for n activities that are not sorted?

Show answer
O(n log n)

Sorting by finish is O(nlog⁡n)O(n \log n), then a single scan is O(n)O(n). The total is O(nlog⁡n)O(n \log n). If the input is already sorted it is O(n)O(n).

Question 6medium

Fractional knapsack, capacity 15. Items (weight, value): A(6, 30), B(10, 40), C(4, 28), D(8, 24). What is the maximum total value?

Show answer
78

Ratios (value/weight): C 7, A 5, B 4, D 3. Take from largest to smallest:
All of C: 4 kg, 28. 11 left.
All of A: 6 kg, 30, total 58. 5 left.
5/10 of B: 40 × 0.5 = 20, total 78. The knapsack is full.

Question 7easy

Same items, fractional knapsack with capacity 20. Maximum value?

Show answer
98

C (4, 28) + A (6, 30) + all of B (10, 40) = 20 kg, 98. The knapsack is exactly full, no room is left for D.

Question 8hard

Same items, but now 0/1 knapsack (take an item whole or not at all), capacity 15. What does the greedy-by-ratio rule get, and what is the true optimum?

Show answer
Greedy: C + A = 58; optimum: B + C = 68

Greedy by ratio takes C (4 kg) and A (6 kg), 5 kg left. B (10) and D (8) do not fit: 58.
Trying all subsets, the best is B + C: 14 kg, 68.
The 0/1 knapsack does not have the greedy-choice property; the solution is dynamic programming (O(nW)O(nW)).

Question 9easy

Coins {25, 10, 5, 1}. How many coins does the greedy algorithm use for 68, and which ones?

Show answer
7 coins: 25, 25, 10, 5, 1, 1, 1

At each step the largest coin that fits: 68 → 43 → 18 → 8 → 3 → 2 → 1 → 0. For this system greedy is optimal (DP also finds 7).

Question 10medium

Coins {1, 4, 6}, amount 8. Greedy result vs optimal?

Show answer
Greedy: 6 + 1 + 1 = 3 coins; optimal: 4 + 4 = 2 coins

Greedy takes 6 first, and the remaining 2 can only be paid with 1s. 4 + 4 uses fewer coins. Greedy coin change does not work in every system; the system must be 'canonical'. The general solution is DP: dp[a] = min(dp[a-c] + 1).

Question 11hard

Write a method int maxActivities(int[] s, int[] f) that returns the maximum number of non-overlapping activities (an activity may start when the previous ends). Arrays are not sorted.

Show answer
Sort indices by finish time, then take every activity whose start ≥ last finish

static int maxActivities(int[] s, int[] f) {
    int n = s.length;
    Integer[] idx = new Integer[n];
    for (int i = 0; i < n; i++) idx[i] = i;
    Arrays.sort(idx, (a, b) -> Integer.compare(f[a], f[b]));
    int count = 0, lastFinish = Integer.MIN_VALUE;
    for (int i : idx) {
        if (s[i] >= lastFinish) {
            count++;
            lastFinish = f[i];
        }
    }
    return count;
}
Do not sort s and f separately; the pairs get broken. Sort the indices. It returns 5 for the 9 activities on the page.

Question 12hard

Write a method double fractionalKnapsack(int[] w, int[] v, int cap) that returns the maximum value when items may be split.

Show answer
Sort by v/w descending; take whole items while they fit, then a fraction of the next

static double fractionalKnapsack(int[] w, int[] v, int cap) {
    int n = w.length;
    Integer[] idx = new Integer[n];
    for (int i = 0; i < n; i++) idx[i] = i;
    Arrays.sort(idx, (a, b) -> Double.compare((double) v[b] / w[b], (double) v[a] / w[a]));
    double total = 0;
    int left = cap;
    for (int i : idx) {
        if (left == 0) break;
        int take = Math.min(left, w[i]);
        total += (double) v[i] * take / w[i];
        left -= take;
    }
    return total;
}
If you compute the ratio as v[i] / w[i] with int division, the ratios in this example happen to come out as whole numbers, but an item of 7 kg worth 25 would have a ratio of 3 (instead of 3.57) and the order could break; convert to double. In the example on the page it returns 78.0.

Verification: Activity selection (greedy by finish order + the optimum by 2^n brute force), counterexamples, fractional and 0/1 knapsack (greedy + brute force trying all subsets), and coin change (greedy + DP) were run with verify/SEN2212/W12.java (java W12.java). The write-a-method answers were compiled and tested in verify/SEN2212/WMethods11to14.java.

Review card

GREEDY: at each step pick what looks best right now, never go back To be correct: greedy-choice property + optimal substructure To show it is wrong: ONE counterexample is enough Activity selection: take the EARLIEST FINISHING, add if s >= lastFinish earliest start / shortest / fewest conflicts rules are WRONG Time: sorting O(n log n) + scan O(n) Fractional knapsack: by v/w ratio descending, split the last piece → optimal 0/1 knapsack: greedy is WRONG, DP O(nW) example cap 15: fractional 78, 0/1 greedy 58, 0/1 optimum 68 Coin change {25,10,5,1}: greedy is optimal (68 → 7 coins) {1,4,6} with 8: greedy 6+1+1=3, optimum 4+4=2 Huffman, Prim, Kruskal, Dijkstra are also greedy algorithms