Table of Contents
Introduction
In this tutorial, we will explore two classic problems that can be solved using greedy algorithms: the Activity Selection Problem and the Fractional Knapsack Problem. Greedy algorithms make locally optimal choices at each step with the hope of finding a global optimum solution. By the end of this tutorial, you will understand the concepts behind these two problems and be able to implement the corresponding algorithms in Python.
Before we proceed, make sure you have the following prerequisites:
- Basic understanding of Python programming language.
- Familiarity with lists and sorting.
- Python installed on your machine.
Activity Selection Problem
Overview
The Activity Selection Problem involves selecting a maximum-sized set of mutually compatible activities from a given set, assuming that the activities are sorted by their finish time. The goal is to find the largest set of non-overlapping activities that can be performed.
Algorithm
To solve the Activity Selection Problem, we can use a greedy algorithm that iteratively selects activities with the earliest finish time. Here’s the high-level algorithm:
- Sort the activities by their finish time in ascending order.
- Initialize an empty list
selected_activities
to store the selected activities. - Select the first activity from the sorted list (since it has the earliest finish time) and add it to
selected_activities
. - For each remaining activity, if the start time is greater than or equal to the finish time of the last selected activity, add it to
selected_activities
. - Repeat step 4 until all activities are considered.
- Return
selected_activities
as the maximum-sized set of non-overlapping activities.
Implementation
Let’s implement the Activity Selection algorithm in Python: ```python def activity_selection(activities): activities.sort(key=lambda x: x[1]) # Sort by finish time selected_activities = [activities[0]] # Select the first activity
for activity in activities[1:]:
if activity[0] >= selected_activities[-1][1]:
selected_activities.append(activity)
return selected_activities
``` ### Example
Consider the following list of activities, where each activity is represented as a tuple (start_time, finish_time)
:
python
activities = [(1, 4), (5, 7), (2, 6), (8, 10), (9, 11), (3, 9)]
By applying the Activity Selection algorithm, we can find the maximum-sized set of non-overlapping activities:
python
selected = activity_selection(activities)
print(selected) # Output: [(1, 4), (5, 7), (8, 10)]
In this example, the activities (1, 4)
, (5, 7)
, and (8, 10)
can be performed without any overlap, providing the maximum-sized set.
Fractional Knapsack Problem
Overview
The Fractional Knapsack Problem involves selecting a combination of items (with fractional quantities) to maximize the total value within a given capacity constraint. Each item has both a weight and a value associated with it.
Algorithm
To solve the Fractional Knapsack Problem, we can use a greedy algorithm that selects items based on their value-to-weight ratio. Here’s the high-level algorithm:
- Compute the value-to-weight ratio for each item.
- Sort the items in descending order of their value-to-weight ratios.
- Initialize the result
selected_items
as an empty list. - For each item, if the weight of the current item is less than or equal to the remaining capacity, add it to
selected_items
and subtract its weight from the remaining capacity. - If the weight of the current item is greater than the remaining capacity, calculate the fraction of the item that can be added to
selected_items
based on the remaining capacity and add it. - Repeat steps 4 and 5 until all items are considered or the remaining capacity becomes zero.
- Return
selected_items
as the selected combination of items.
Implementation
Let’s implement the Fractional Knapsack algorithm in Python: ```python def fractional_knapsack(items, capacity): items.sort(key=lambda x: x[1] / x[0], reverse=True) # Sort by value-to-weight ratio selected_items = []
for item in items:
if capacity >= item[0]:
selected_items.append(item)
capacity -= item[0]
else:
fraction = capacity / item[0]
selected_items.append((item[0] * fraction, item[1] * fraction))
break
return selected_items
``` ### Example
Consider the following list of items, where each item is represented as a tuple (weight, value)
:
python
items = [(10, 60), (20, 100), (30, 120)]
capacity = 50
By applying the Fractional Knapsack algorithm, we can find the selected combination of items:
python
selected = fractional_knapsack(items, capacity)
print(selected) # Output: [(20, 100), (30, 120)]
In this example, the total weight of the selected items (20, 100)
and (30, 120)
is equal to the remaining capacity of 50, providing the maximum total value.
Conclusion
In this tutorial, we explored two classic problems that can be solved using greedy algorithms: the Activity Selection Problem and the Fractional Knapsack Problem. We learned the concepts behind these problems and implemented the corresponding algorithms in Python. By applying these algorithms, we can efficiently find optimal solutions in various scenarios.
Remember, greedy algorithms make locally optimal choices, but they might not always lead to the globally optimal solution. It’s important to thoroughly understand the problem and analyze the given constraints before applying greedy algorithms.
Now that you have a deeper understanding of greedy algorithms and their applications, you can further explore other problems where greedy strategies can be employed. Keep practicing and experimenting with different algorithms to enhance your problem-solving skills.