14.5 Activity Selection

The activity selection problem is historically one of the first optimization problems used to demonstrate the power of greedy algorithms.

14.5 Activity Selection

The activity selection problem is historically one of the first optimization problems used to demonstrate the power of greedy algorithms. Although it is mathematically equivalent to interval scheduling, it deserves separate study because it introduces a broader design pattern:

When resources are limited and tasks compete for those resources, selecting the task that leaves the most flexibility for future decisions often leads to an optimal solution.

This principle appears repeatedly throughout greedy algorithm design, from CPU scheduling and network allocation to manufacturing systems and project management.

Problem

A resource can serve only one activity at a time.

Given a collection of activities:

$$ (s_i, f_i) $$

where:

  • (s_i) is the start time
  • (f_i) is the finish time

Select the largest possible set of mutually compatible activities.

Two activities are compatible if:

$$ s_j \ge f_i $$

or

$$ s_i \ge f_j $$

for every selected pair.

Example

Consider:

Activity Start Finish
A 1 4
B 3 5
C 0 6
D 5 7
E 3 9
F 5 9
G 6 10
H 8 11
I 8 12
J 2 14
K 12 16

Goal:

Select the maximum number of compatible activities.

Visual Representation

A  [1-----4]
B    [3-----5]
C [0---------6]
D         [5---7]
E    [3---------9]
F         [5-------9]
G           [6-------10]
H               [8-----11]
I               [8-------12]
J   [2---------------14]
K                     [12------16]

Many activities overlap.

Only a subset can be chosen.

Greedy Observation

Suppose we choose an activity that ends late.

[1----------------12]

Many future opportunities disappear.

Suppose instead we choose an activity that ends early.

[1---3]

More future activities remain available.

This suggests the greedy rule:

Always select the activity that finishes earliest.

This is identical to the interval scheduling strategy.

Algorithm

Step 1

Sort activities by finish time.

Step 2

Select the earliest-finishing activity.

Step 3

Discard all overlapping activities.

Step 4

Repeat on the remaining activities.

Pseudo-code:

Sort by finish time

Choose first activity

For each activity:
    If activity.start >= current_finish:
        Choose activity

Walkthrough

Sort the example by finish time.

Activity Finish
A 4
B 5
C 6
D 7
E 9
F 9
G 10
H 11
I 12
J 14
K 16

Select A

A

Current finish:

4

Scan Remaining Activities

B overlaps.

C overlaps.

D starts at 5.

Select D.

A -> D

Current finish:

7

Continue

E overlaps.

F overlaps.

G overlaps.

H starts at 8.

Select H.

A -> D -> H

Current finish:

11

Continue

I overlaps.

J overlaps.

K starts at 12.

Select K.

Final solution:

A -> D -> H -> K

Four activities.

No larger feasible solution exists.

Why Earliest Finish Is Optimal

The proof follows the exchange argument introduced earlier.

Let:

$$ g $$

be the earliest-finishing activity.

Assume an optimal solution begins with another activity:

$$ o $$

Because:

$$ f(g) \le f(o) $$

replace (o) with (g).

Every later activity that was compatible with (o) remains compatible with (g).

Therefore:

  • Feasibility is preserved.
  • Number of activities is unchanged.

An optimal solution exists that begins with the greedy choice.

After selecting (g), the remaining problem is identical to the original problem but smaller.

The argument repeats recursively.

Recursive View

The algorithm repeatedly solves:

Select the earliest-finishing activity.

Then:

Solve the remaining activity selection problem beginning after that finish time.

The recursive structure explains why the greedy algorithm works.

Each local choice creates a smaller instance of the same optimization problem.

Iterative Implementation

The recursive formulation is useful for proofs.

Practical implementations use iteration.

Go

type Activity struct {
	Start  int
	Finish int
}

func SelectActivities(a []Activity) []Activity {
	sort.Slice(a, func(i, j int) bool {
		return a[i].Finish < a[j].Finish
	})

	if len(a) == 0 {
		return nil
	}

	result := []Activity{a[0]}
	lastFinish := a[0].Finish

	for i := 1; i < len(a); i++ {
		if a[i].Start >= lastFinish {
			result = append(result, a[i])
			lastFinish = a[i].Finish
		}
	}

	return result
}

Complexity Analysis

Sorting:

$$ O(n \log n) $$

Selection scan:

$$ O(n) $$

Total:

$$ O(n \log n) $$

Space:

$$ O(1) $$

excluding the output list.

Why Other Greedy Rules Fail

Earliest Start Time

Example:

Activity Start Finish
A 1 10
B 2 3
C 3 4
D 4 5

Choosing A first yields:

A

One activity.

Optimal:

B -> C -> D

Three activities.

Shortest Duration

Example:

Activity Start Finish
A 1 4
B 4 7
C 3 5

Shortest duration chooses C.

Optimal solution chooses:

A -> B

The heuristic fails.

Least Overlap

Local conflict counts do not necessarily maximize future opportunities.

Counterexamples are easy to construct.

Earliest Finish Principle

A useful way to remember the proof is:

Every activity occupies time.

Earlier finishing activities occupy less future time.

Less occupied future time means more scheduling opportunities.

The greedy algorithm always preserves the largest possible future search space.

Applications

Conference Scheduling

Assign presentations to a room.

Manufacturing Systems

Schedule machine operations.

Reservation Systems

Allocate resources to customers.

CPU Job Scheduling

Execute tasks without overlap.

Satellite Communication

Assign transmission windows.

Airport Runways

Schedule takeoffs and landings.

Each application reduces to selecting a maximum-size compatible subset.

Relationship to Interval Scheduling

Many texts treat interval scheduling and activity selection as identical problems.

In practice:

  • Interval scheduling emphasizes intervals.
  • Activity selection emphasizes tasks competing for a resource.

The underlying algorithm remains the same.

Studying both viewpoints helps build intuition that transfers to more complex scheduling problems.

Takeaway

The activity selection problem demonstrates one of the most important principles in greedy optimization: choose the option that leaves the most room for future decisions. Sorting activities by finishing time and repeatedly selecting the earliest-finishing compatible activity produces an optimal solution in (O(n \log n)) time. The correctness proof follows directly from an exchange argument showing that every optimal schedule can be transformed to begin with the greedy choice. This pattern becomes the foundation for many scheduling and resource-allocation algorithms encountered later in algorithm design.