Greedy Algorithms

Make the locally best choice at every step. Activity selection, fractional knapsack and Huffman coding.

A greedy algorithm builds a solution step by step, always taking the choice that looks best right now, and never going back. Greedy algorithms are simple and fast, but they only give the optimal answer for problems with two properties:

  • Greedy-choice property: some optimal solution starts with the greedy choice.
  • Optimal substructure: after the greedy choice, what is left is a smaller instance of the same problem.

Kruskal's and Prim's MST algorithms and Dijkstra's shortest paths are greedy. This lesson adds three classics.

Activity selection

Given activities with start and finish times, choose the largest number that do not overlap. Greedy rule: sort by finish time and always take the next activity that starts after the last chosen one finishes. Picking the earliest finish leaves the most room for the rest.

import java.util.*;

public class Main {
    public static void main(String[] args) {
        int[][] acts = {{1, 4}, {3, 5}, {0, 6}, {5, 7}, {3, 9}, {5, 9}, {6, 10}, {8, 11}, {8, 12}, {2, 14}, {12, 16}};
        Arrays.sort(acts, Comparator.comparingInt(a -> a[1]));      // by finish time
        List<String> chosen = new ArrayList<>();
        int lastFinish = -1;
        for (int[] a : acts) {
            if (a[0] >= lastFinish) {
                chosen.add("[" + a[0] + "," + a[1] + ")");
                lastFinish = a[1];
            }
        }
        System.out.println(chosen.size() + " activities: " + String.join(" ", chosen));
    }
}
Output
4 activities: [1,4) [5,7) [8,11) [12,16)

Fractional knapsack

Items have weights and values, and you may take fractions of items (like grains or liquids). Greedy rule: take items in decreasing order of value per unit weight, and take a fraction of the last one to fill the bag exactly. This is optimal, unlike the 0/1 knapsack, where items cannot be split and greedy can fail (that one needs dynamic programming).

import java.util.*;

public class Main {
    public static void main(String[] args) {
        double[][] items = {{10, 60}, {20, 100}, {30, 120}};       // {weight, value}
        double capacity = 50, total = 0;
        Arrays.sort(items, (a, b) -> Double.compare(b[1] / b[0], a[1] / a[0]));   // best ratio first
        for (double[] it : items) {
            if (capacity <= 0) break;
            double take = Math.min(it[0], capacity);
            total += take * it[1] / it[0];
            capacity -= take;
            System.out.printf("take %.0f of weight %.0f (ratio %.1f)%n", take, it[0], it[1] / it[0]);
        }
        System.out.printf("total value = %.1f%n", total);
    }
}
Output
take 10 of weight 10 (ratio 6.0)
take 20 of weight 20 (ratio 5.0)
take 20 of weight 30 (ratio 4.0)
total value = 240.0

With the same items in a 0/1 knapsack of capacity 50, the best is items 2 and 3 for value 220; greedy by ratio would take items 1 and 2 for only 160.

Huffman coding

To compress text, give frequent characters short binary codes. Huffman's algorithm builds an optimal prefix code (no code is the start of another, so decoding is unambiguous):

  1. Put every character in a min-priority queue keyed by frequency.
  2. Repeatedly remove the two smallest, join them under a new node whose frequency is their sum, and put it back.
  3. When one tree is left, left edges are 0 and right edges are 1; each character's code is its root-to-leaf path.
100a45 → code 05525c12 → 100b13 → 1013014f5 → 1100e9 → 1101d16 → 111
Huffman tree for a:45 b:13 c:12 d:16 e:9 f:5 (frequencies in the note)
100a45 → code 05525c12 → 100b13 → 1013014f5 → 1100e9 → 1101d16 → 111
Huffman tree for a:45 b:13 c:12 d:16 e:9 f:5 (frequencies in the note)
import java.util.*;

public class Main {
    record Node(int freq, int order, char ch, Node left, Node right) { }

    static void codes(Node n, String prefix, Map<Character, String> out) {
        if (n.left() == null) { out.put(n.ch(), prefix); return; }
        codes(n.left(), prefix + "0", out);
        codes(n.right(), prefix + "1", out);
    }

    public static void main(String[] args) {
        char[] chars = {'a', 'b', 'c', 'd', 'e', 'f'};
        int[] freq = {45, 13, 12, 16, 9, 5};
        PriorityQueue<Node> pq = new PriorityQueue<>(Comparator.comparingInt(Node::freq).thenComparingInt(Node::order));
        int order = 0;
        for (int i = 0; i < chars.length; i++) pq.add(new Node(freq[i], order++, chars[i], null, null));
        while (pq.size() > 1) {
            Node x = pq.poll(), y = pq.poll();
            pq.add(new Node(x.freq() + y.freq(), order++, '-', x, y));
        }
        Map<Character, String> code = new TreeMap<>();
        codes(pq.poll(), "", code);
        int bits = 0;
        for (int i = 0; i < chars.length; i++) bits += freq[i] * code.get(chars[i]).length();
        System.out.println("codes: " + code);
        System.out.println("total bits = " + bits + " (fixed 3-bit code: " + 3 * 100 + ")");
    }
}
Output
codes: {a=0, b=101, c=100, d=111, e=1101, f=1100}
total bits = 224 (fixed 3-bit code: 300)

The average code length is 224 / 100 = 2.24 bits per character, against 3 bits for a fixed-length code. Ties between equal frequencies can produce different codes, but the total number of bits is always the same optimal value.

GATE-style practice questions

GATE-styleNAT · numerical answer

Characters with frequencies a:5, b:9, c:12, d:13, e:16, f:45 are Huffman coded. What is the total number of bits needed to encode a message containing exactly these counts (100 characters)?

Show answer and explanationAnswer: 224

The total encoded length equals the sum of all merge costs: 5 + 9 = 14, 12 + 13 = 25, 14 + 16 = 30, 25 + 30 = 55, 45 + 55 = 100. Adding them, 14 + 25 + 30 + 55 + 100 = 224 bits.

GATE-styleNAT · numerical answer

In a fractional knapsack of capacity 15, items have (weight, value) = (5, 10), (10, 40), (4, 20), (6, 18). What is the maximum total value?

Show answer and explanationAnswer: 63

Ratios: 20/4 = 5, 40/10 = 4, 18/6 = 3, 10/5 = 2. Take the weight-4 item (20) and the weight-10 item (40): weight 14, value 60. One unit of capacity is left, so take 1/6 of the (6, 18) item for 3 more. Total 63.

GATE-styleMSQ · one or more correct answers

For which problems does the greedy strategy described in this lesson always give an optimal solution? (Select all that apply.)

Show answer and explanationAnswer: A, B, D

Activity selection, fractional knapsack and MST all have the greedy-choice property. For 0/1 knapsack, greedy can fail: with capacity 50 and items (10, 60), (20, 100), (30, 120), greedy gets 160 but the optimum is 220.