Implement a Greedy Algorithm for Activity Selection Problem

problem-solving
Published

December 23, 2024

The Activity Selection Problem is a classic algorithmic puzzle where you’re given a set of activities, each with a start and finish time, and the goal is to select the maximum number of non-overlapping activities. This is a perfect scenario to demonstrate the power of greedy algorithms – algorithms that make the locally optimal choice at each stage with the hope of finding a global optimum.

Understanding the Problem

Imagine you have a list of activities:

Activity Start Time Finish Time
A 1 4
B 3 5
C 0 6
D 5 7
E 3 8
F 5 9
G 6 10

You can’t select activities B and C because they overlap. The challenge is to find the largest subset of activities that are mutually compatible (no overlap).

The Greedy Approach

The greedy strategy for this problem is straightforward:

  1. Sort: Sort the activities by their finish times in ascending order.
  2. Select: Select the first activity (the one with the earliest finish time).
  3. Iterate: Iterate through the remaining activities. Select an activity only if its start time is greater than or equal to the finish time of the previously selected activity.

This approach ensures that we always pick the activity that leaves the maximum time for subsequent activities.

Python Implementation

Here’s a Python function that implements this greedy algorithm:

def activity_selection(activities):
    """
    Selects the maximum number of non-overlapping activities using a greedy algorithm.

    Args:
        activities: A list of tuples, where each tuple represents an activity 
                     in the format (start_time, finish_time, activity_name).

    Returns:
        A list of selected activities.
    """

    # Sort activities by finish time
    activities.sort(key=lambda x: x[1])  

    selected_activities = []
    last_finish_time = 0

    for start, finish, name in activities:
        if start >= last_finish_time:
            selected_activities.append((start, finish, name))
            last_finish_time = finish

    return selected_activities

# Example usage:
activities = [(1, 4, 'A'), (3, 5, 'B'), (0, 6, 'C'), (5, 7, 'D'), (3, 8, 'E'), (5, 9, 'F'), (6, 10, 'G')]
selected = activity_selection(activities)
print("Selected activities:", selected)

This code first sorts the activities by their finish times. Then, it iterates through the sorted list, adding an activity to the selected_activities list only if it doesn’t overlap with the previously selected activity. The output clearly shows the selected, non-overlapping activities.

Optimizations and Considerations

While this greedy approach is efficient (O(n log n) due to sorting), it only guarantees a solution; it doesn’t necessarily find the absolute best solution for all possible variations of the problem. For certain problem instances, other algorithms might yield a more optimal outcome. However, for the standard activity selection problem, the greedy approach is both efficient and effective.