Chapter 10

Greedy Algorithms

A greedy algorithm always makes the choice that looks best at the moment. This chapter develops that idea through fractional knapsack, Huffman coding, job sequencing with deadlines, and the optimal merge pattern.

10.1Introduction to Greedy Algorithms

Core idea A greedy algorithm builds a solution step by step by choosing the best feasible option at the current stage. For problems with the greedy-choice property and optimal substructure, these choices produce a globally optimal solution.
From many solutions to an optimal solution
Optimization problems impose constraints; feasible solutions are filtered before the best one is chosen.
Maximize

Profit, reliability, trust, and efficiency.

Minimize

Cost, risk, and latency.

Greedy-choice property

A locally optimal choice can be committed to without preventing a globally optimal solution.

Optimal substructure

An optimal solution contains optimal solutions to its subproblems.

Why use it? When both properties hold, the greedy method often produces an efficient solution.

10.2Steps to Design a Greedy Algorithm

1 Choose a strategy

Decide the sorting rule, selection criterion, or priority.

2 Iterate in stages

Consider the input elements one by one in greedy order.

3 Check feasibility

Add a choice only when it keeps the partial solution feasible.

GREEDY(A):
    candidates = order_by_greedy_rule(A)
    solution = []

    for candidate in candidates:
        if feasible(candidate, solution):
            solution.append(candidate)

    return solution
Final step After all stages, return the constructed feasible solution.

10.3Fractional Knapsack Problem

Input and goal Given n items with weights and profits and a knapsack of capacity W, maximize total profit. Fractions of items are allowed. In the first lecture example, n = 3 and the maximum bag capacity is m = 20.
ItemItem 1Item 2Item 3
Profit252415
Weight181510
Profit / weight≈ 1.391.601.50
Which greedy choice works?
Knapsack capacity = 20. Compare all three strategies from the slides.
capacity 20
Profit: 0
Greedy by profit

Item 1 first earns 25; adding 2/15 of Item 2 gives 28.2.

Greedy for more items

Item 3 + 10/15 of Item 2 gives 31.

Greedy by profit/weight

Item 2 first earns 24; adding 5/10 of Item 3 gives the optimum 31.5.

Second knapsack example

ItemItem 1Item 2Item 3Capacity
Profit6010012050
Weight102030
Fill the 50-unit knapsack by decreasing profit/weight
Ratios are 6, 5, and 4, so take Item 1, Item 2, then 20/30 of Item 3.
capacity 50
Profit: 0
Result Item 1 and Item 2 are taken completely, followed by 20/30 of Item 3. The maximum profit is 60 + 100 + 80 = 240.
def fractional_knapsack(items, capacity):
    for item in items:
        item.ratio = item.profit / item.weight

    items.sort(key=lambda item: item.ratio, reverse=True)
    total_profit = 0

    for item in items:
        if capacity == 0:
            break

        if item.weight <= capacity:
            capacity -= item.weight
            total_profit += item.profit
        else:
            total_profit += item.profit * (capacity / item.weight)
            break

    return total_profit

10.4Huffman Coding

Greedy compression Huffman coding is a greedy algorithm for lossless data compression. It assigns variable-length codes according to frequency: more frequent characters receive shorter codes. Depending on the data, it can save roughly 20% to 90% of storage.

1. Problem data

CharacterAEIOU
Frequency57364
Input size

The five character frequencies add up to 25 characters.

ASCII baseline

For this comparison, each character is stored in one 8-bit byte, so the data requires 25 × 8 = 200 bits.

Questions from the slides Can we reduce the total bits?
Can we still reduce the total bits?

2. Huffman solution approach

  1. Merge the two least frequent nodes into a new node whose frequency is their sum.
  2. Put the new node back and repeat until one root remains.
  3. Add 0 to every left edge.
  4. Add 1 to every right edge.
  5. Traverse from the root to each leaf to obtain its Huffman code.
Construct the exact coding tree
Merge the two least frequent nodes, repeat, label left edges 0 and right edges 1, then traverse root to leaf.
E = 00 I = 010 U = 011 A = 10 O = 11

3. Final answer — the lecture’s Huffman codes

CharacterFrequencyCodeData bits
A51010
E70014
I30109
O61112
U401112
Total2512 mapping bits57

4. Verify the storage saving

CharacterFrequencyASCIIASCII bits Fixed 3-bitFixed bitsHuffmanHuffman bits
A50100000140000151010
E70100010156001210014
I3010010012401090109
O60100111148011181112
U401010101321001201112
Total25—200—75—57
Fixed-code mapping table

Five ASCII symbols use 40 bits and five 3-bit codes use 15 bits: mapping overhead = 55 bits.

Huffman mapping table

Five ASCII symbols use 40 bits and the five variable codes use 12 bits: mapping overhead = 52 bits.

Compare the exact bit totals from the slides
ASCII, fixed 3-bit encoding with its mapping table, and Huffman encoding with its mapping table.
ASCII

25 characters × 8 bits = 200 bits.

Fixed 3-bit

Five characters need ⌈log₂ 5⌉ = 3 bits each. Data 75 + symbols 40 + codes 15 = 130 bits.

Huffman

Data 57 + symbols 40 + codes 12 = 109 bits.

Answers to the slide questions Fixed 3-bit encoding reduces the total from 200 to 130 bits. Huffman coding reduces it further to 109 bits, including its mapping table.

10.5Building a Huffman Tree with a Priority Queue

  1. Create a binary-tree node for every character and frequency.
  2. Place the nodes in a priority queue—a min heap ordered by frequency.
  3. Extract the two minimum nodes, attach them to a new node, and insert their sum.
  4. Repeat until the queue contains one node: the root.
HUFFMAN(C):
    n = |C|
    Q = C
    for i = 1 to n - 1:
        allocate a new node z
        x = EXTRACT_MIN(Q)
        y = EXTRACT_MIN(Q)
        z.left = x
        z.right = y
        z.freq = x.freq + y.freq
        INSERT(Q, z)
    return EXTRACT_MIN(Q)  # the root is the only node left
Priority-queue construction for I:3, U:4, A:5, O:6, E:7
Lower frequency means higher priority.
A = 00 O = 01 E = 10 I = 110 U = 111
Why the printed codes can differ The queue contains E:7 and IU:7. Either may be extracted first. Also, the left and right children of any merged node may be swapped. These choices change the printed 0/1 patterns, but not the code lengths, prefix property, or weighted total of 57 bits.

Classic six-character construction

The second tree from the slides starts with f:5, e:9, c:12, b:13, d:16, a:45.

a = 0 c = 100 b = 101 f = 1100 e = 1101 d = 111

Encoding and decoding: FACE

Using the same six frequencies and tree shape above, relabel the symbols as A:5, B:9, C:12, D:13, E:16 and F:45. With left edges labelled 0 and right edges labelled 1, the mapping is:

SymbolFrequencyCode
A51100
B91101
C12100
D13101
E16111
F450
Encoding FACE → 0 | 1100 | 100 | 111 → 01100100111
Follow each bit. Recover each symbol.
0 goes left · 1 goes right · A leaf completes a character.
FACE / 11 bits
Current code—
Decoded text—
Bits read0 / 11

Decompression

The decoder needs the mapping table (or the equivalent Huffman tree) and the compressed bitstream.

  1. Read bits from left to right, adding one bit at a time to the current code.
  2. When the code matches a symbol in the table, output that symbol and clear the current code.
  3. Repeat until the bitstream has been decoded.
Bits read until a matchOutputRemaining bits
0F1100100111
1 → 11 → 110 → 1100A100111
1 → 10 → 100C111
1 → 11 → 111ENone
Why separators are unnecessary Huffman codes are prefix-free: no valid code is the beginning of another valid code. For example, 0 already means F, so no other code starts with 0; 1, 11 and 110 are incomplete, so reading continues until 1100 identifies A. Each boundary is unambiguous. The bars above are only reading aids; 01100100111 decodes to FACE without separators.

10.6Job Sequencing with Deadlines

Problem Each unit-time job has a deadline and a profit earned only when the job finishes by that deadline. Schedule jobs to maximize total profit.
Applications

Operating-system task scheduling, project management, and manufacturing processes.

Capacity of the schedule

If the highest deadline is k, at most k unit-time jobs can be scheduled.

Brute force Trying all job permutations takes O(n!) time and is impractical for large n.

Greedy solution

  1. Sort the jobs in decreasing order of profit.
  2. For each job, place it in the latest empty slot that is no later than its deadline.
Schedule each job in the latest available slot
Sort by decreasing profit, then scan backward from each deadline.
Total profit: 0

Results from the slides

ExampleSchedule by time slotTotal profit
First feasible arrangementJ1, J4, J320 + 15 + 15 = 50
Greedy lecture solutionJ2, J3, J118 + 15 + 20 = 53
Exercise solutionC, A, E27 + 100 + 15 = 142
Deadline check J2 has deadline 1, so it must occupy the first slot. In the lecture’s 53-profit schedule, J3 occupies the second slot and J1 occupies the third. Select the exercise dataset above to replay C, A, E.
Time complexity Sorting costs O(n log n). With a simple backward scan, job placement costs O(n²), so the implementation shown in the slides is O(n²). A disjoint-set structure can locate the latest free slot efficiently, reducing the overall bound to O(n log n) after sorting.

10.7Optimal Merge Pattern

Before optimizing a sequence of file merges, recall how two sorted arrays are merged in linear time.

Merge A = [1, 3, 5, 6] and B = [2, 4, 8, 9]
Compare A[i] with B[j], append the smaller element to C[m], and advance one pointer.
O(n + m)
i = j = m = 0
while i < len(A) and j < len(B):
    if A[i] <= B[j]:
        C[m] = A[i]
        i += 1
    else:
        C[m] = B[j]
        j += 1
    m += 1

# Append the remaining elements from A or B.
Problem statement Given n sorted files with sizes S₁, S₂, …, Sₙ, merge them in pairs. Merging two files costs the sum of their sizes. Find the merge order with minimum total cost.
FileABCD
Size3020105

Greedy solution

  1. Insert all file sizes into a min heap (priority queue).
  2. Extract the two smallest files.
  3. Merge them; the merge cost is the sum of their sizes.
  4. Insert the merged file back into the heap.
  5. Repeat until one file remains; add every merge cost to the total.
Always merge the two smallest files first
A min heap keeps the next two files at the front of the priority queue.
Total merge cost: 0

Cost comparison from the slides

Order 1 50 + 60 + 65 = 175
Order 2 50 + 15 + 65 = 130
Two-smallest rule 15 + 35 + 65 = 115
Build heapO(n)
Two extractsO(log n)
Insert sumO(log n)
TotalO(n log n)
Equal file sizes When equal-size files tie for the smallest values, either tied file may be chosen. Continue applying the two-smallest rule after every merge; an arbitrary unbalanced merge order is not generally equivalent.
Real-world applications Huffman coding, external sorting for large database datasets, and data-merge operations such as log-file merging and distributed computing.
Homework If the total file size is n and there are exactly four subfiles, what is the worst-case merging time in terms of n?
Reveal the answer

With total file size n and exactly four subfiles, there are three merges. Since four is constant, the worst-case merging time is Θ(n) in terms of n.