208 lines
No EOL
11 KiB
Text
208 lines
No EOL
11 KiB
Text
---
|
|
id: greedy
|
|
title: "Greedy Algorithms"
|
|
author: Darren Yao
|
|
prerequisites:
|
|
- Silver - Sorting with Custom Comparators
|
|
- Silver - Introduction to Ordered Sets
|
|
description: Greedy algorithms select the optimal choice at each step instead of looking at the solution space as a whole. This reduces the problem to a smaller problem at each step.
|
|
frequency: 3
|
|
---
|
|
|
|
import {Problem} from "../models"
|
|
|
|
export const metadata = {
|
|
problems: {
|
|
movie: [
|
|
new Problem("CSES", "Movie Festival", "1629", "Easy", false, [], ""),
|
|
],
|
|
cses: [
|
|
new Problem("CSES", "Apartments", "1084", "Easy", false, [], "Sort applicants and apartments, then greedily assign applicants"),
|
|
new Problem("CSES", "Ferris Wheel", "1090", "Easy", false, [], "Sort children, keep a left pointer and a right pointer. Each gondola either is one child from the right pointer or two children, one left and one right."),
|
|
new Problem("CSES", "Towers", "1073", "Easy", false, ["multiset", "greedy"]),
|
|
new Problem("CSES", "Room Allocation", "1164", "Normal", false, ["multiset", "greedy"], "similar to above"),
|
|
new Problem("CSES", "Tasks & Deadlines", "1630", "Easy", false, [], ""),
|
|
new Problem("CSES", "Movie Festival II", "1632", "Easy", false, [], ""),
|
|
new Problem("CSES", "Stick Division", "1161", "Hard", false, [], ""),
|
|
],
|
|
usaco: [
|
|
new Problem("Silver", "Lemonade Line", "835", "?", false, [], ""),
|
|
new Problem("Silver", "Why Did the Cow Cross the Road", "714", "?", false, [], "first step: sort!"),
|
|
new Problem("Silver", "Berry Picking", "990", "?", false, [], ""),
|
|
new Problem("Silver", "Rest Stops", "810", "?", false, [], ""),
|
|
new Problem("Silver", "High Card Wins", "571", "?", false, [], ""),
|
|
],
|
|
other: [
|
|
new Problem("CSA", "Sure Bet", "sure-bet", "?", false, [], ""),
|
|
new Problem("CF", "Did you Mean...", "contest/860/problem/A", "?", false, [], ""),
|
|
new Problem("CF", "Bus", "contest/864/problem/C", "?", false, [], ""),
|
|
new Problem("CF", "Permutation", "contest/864/problem/D", "?", false, [], ""),
|
|
new Problem("CF", "Kayaking", "contest/863/problem/B", "?", false, [], "Huffman Coding?"),
|
|
new Problem("CF", "New Year and Three Musketeers", "contest/611/problem/E", "Hard", false, [], "needs maps"),
|
|
]
|
|
}
|
|
};
|
|
|
|
<resources>
|
|
<resource source="IUSACO" title="9 - Greedy Algorithms" starred>Text below is based off this.</resource>
|
|
<resource source="CPH" title="6 - Greedy Algorithms" starred></resource>
|
|
<resource source="PAPS" title="8 - Greedy Algorithms"></resource>
|
|
<resource source="CPC" title="5 - Greedy Algorithms" url="05_greedy_algorithms"></resource>
|
|
</resources>
|
|
|
|
<br />
|
|
|
|
**Greedy** does not refer to a single algorithm, but rather a way of thinking that is applied to problems. There's no one way to do greedy algorithms. Hence, we use a selection of well-known examples to help you understand the greedy paradigm.
|
|
|
|
Usually, when using a greedy algorithm, there is a **heuristic** or **value function** that determines which choice is considered most optimal. Usually (but not always) some sorting step is involved.
|
|
|
|
(what's a heuristic or value function?)
|
|
|
|
## Example: Studying Algorithms
|
|
|
|
### Statement
|
|
|
|
Steph wants to improve her knowledge of algorithms over winter break. She has a total of $X$ ($1 \leq X \leq 10^4$) minutes to dedicate to learning algorithms. There are $N$ ($1 \leq N \leq 100$) algorithms, and each one of them requires $a_i$ ($1 \leq a_i \leq 100$) minutes to learn. Find the maximum number of algorithms she can learn.
|
|
|
|
### Solution
|
|
|
|
The first observation we make is that Steph should prioritize learning algorithms from easiest to hardest; in other words, start with learning the algorithm that requires the least amount of time, and then choose further algorithms in increasing order of time required. Let's look at the following example:
|
|
|
|
$$X = 15, \qquad N = 6, \qquad a_i = \{ 4, 3, 8, 4, 7, 3 \}$$
|
|
|
|
After sorting the array, we have $\{ 3, 3, 4, 4, 7, 8 \}$. Within the maximum of 15 minutes, Steph can learn four algorithms in a total of $3+3+4+4 = 14$ minutes.
|
|
The implementation of this algorithm is very simple. We sort the array, and then take as many elements as possible while the sum of times of algorithms chosen so far is less than $X$. Sorting the array takes $O(N \log N)$ time, and iterating through the array takes $O(N)$ time, for a total time complexity of $O(N \log N)$.
|
|
|
|
```java
|
|
// read in the input, store the algorithms in int[] algorithms
|
|
Arrays.sort(algorithms);
|
|
int count = 0; // number of minutes used so far
|
|
int i = 0;
|
|
while(count + algorithms[i] <= x){
|
|
// while there is enough time, learn more algorithms
|
|
count += algorithms[i];
|
|
i++;
|
|
}
|
|
pw.println(i); // print the ans
|
|
pw.close();
|
|
```
|
|
|
|
## The Scheduling Problem
|
|
|
|
<problems-list problems={metadata.problems.movie} />
|
|
|
|
There are $N$ events, each described by their starting and ending times. Jason would like to attend as many events as possible, but he can only attend one event at a time, and if he chooses to attend an event, he must attend the entire event. Traveling between events is instantaneous.
|
|
|
|
### Bad Greedy: Earliest Starting Next Event
|
|
|
|
One possible ordering for a greedy algorithm would always select the next possible event that begins as soon as possible. Let's look at the following example, where the selected events are highlighted in red:
|
|
|
|
<svg viewBox="0 0 200 40">
|
|
<line x1="10" y1="5" x2="80" y2="5" style="stroke:rgb(255,0,0);stroke-width:1" />
|
|
<line x1="40" y1="15" x2="120" y2="15" style="stroke:rgb(0,0,0);stroke-width:1" />
|
|
<line x1="110" y1="25" x2="180" y2="25" style="stroke:rgb(255,0,0);stroke-width:1" />
|
|
<line x1="150" y1="35" x2="180" y2="35" style="stroke:rgb(0,0,0);stroke-width:1" />
|
|
</svg>
|
|
|
|
In this example, the greedy algorithm selects two events, which is optimal. However, this doesn't always work, as shown by the following counterexample:
|
|
|
|
<svg viewBox="0 0 200 40">
|
|
<line x1="10" y1="5" x2="160" y2="5" style="stroke:rgb(255,0,0);stroke-width:1" />
|
|
<line x1="40" y1="15" x2="80" y2="15" style="stroke:rgb(0,0,0);stroke-width:1" />
|
|
<line x1="100" y1="25" x2="135" y2="25" style="stroke:rgb(0,0,0);stroke-width:1" />
|
|
<line x1="150" y1="35" x2="180" y2="35" style="stroke:rgb(0,0,0);stroke-width:1" />
|
|
</svg>
|
|
|
|
In this case, the greedy algorithm selects to attend only one event. However, the optimal solution would be the following:
|
|
|
|
<svg viewBox="0 0 200 40">
|
|
<line x1="10" y1="5" x2="160" y2="5" style="stroke:rgb(0,0,0);stroke-width:1" />
|
|
<line x1="40" y1="15" x2="80" y2="15" style="stroke:rgb(255,0,0);stroke-width:1" />
|
|
<line x1="100" y1="25" x2="135" y2="25" style="stroke:rgb(255,0,0);stroke-width:1" />
|
|
<line x1="150" y1="35" x2="180" y2="35" style="stroke:rgb(255,0,0);stroke-width:1" />
|
|
</svg>
|
|
|
|
### Correct Greedy: Earliest Ending Next Event
|
|
|
|
Instead, we can select the event that ends as early as possible. This correctly selects the three events.
|
|
|
|
<svg viewBox="0 0 200 40">
|
|
<line x1="10" y1="5" x2="160" y2="5" style="stroke:rgb(0,0,0);stroke-width:1" />
|
|
<line x1="40" y1="15" x2="80" y2="15" style="stroke:rgb(255,0,0);stroke-width:1" />
|
|
<line x1="100" y1="25" x2="135" y2="25" style="stroke:rgb(255,0,0);stroke-width:1" />
|
|
<line x1="150" y1="35" x2="180" y2="35" style="stroke:rgb(255,0,0);stroke-width:1" />
|
|
</svg>
|
|
|
|
In fact, this algorithm always works. A brief explanation of correctness is as follows. If we have two events $E_1$ and $E_2$, with $E_2$ ending later than $E_1$, then it is always optimal to select $E_1$. This is because selecting $E_1$ gives us more choices for future events. If we can select an event to go after $E_2$, then that event can also go after $E_1$, because $E_1$ ends first. Thus, the set of events that can go after $E_2$ is a subset of the events that can go after $E_1$, making $E_1$ the optimal choice.
|
|
|
|
For the following code, let's say we have the array `events` of events, which each contain a start and an end point. We'll be using the following static class to store each event:
|
|
|
|
```java
|
|
static class Event implements Comparable<Event>{
|
|
int start; int end;
|
|
public Event(int s, int e){
|
|
start = s; end = e;
|
|
}
|
|
public int compareTo(Event e){
|
|
return Integer.compare(this.end, e.end);
|
|
}
|
|
}
|
|
```
|
|
|
|
<br />
|
|
|
|
```java
|
|
// read in the input, store the events in Event[] events.
|
|
Arrays.sort(events); // sorts by comparator we defined above
|
|
int currentEventEnd = -1; // end of event currently attending
|
|
int ans = 0; // how many events were attended?
|
|
for(int i = 0; i < n; i++){ // process events in order of end time
|
|
if(events[i].start >= currentEventEnd){ // if event can be attended
|
|
// we know that this is the earliest ending event that we can attend
|
|
// because of how the events are sorted
|
|
currentEventEnd = events[i].end;
|
|
ans++;
|
|
}
|
|
}
|
|
pw.println(ans);
|
|
pw.close();
|
|
```
|
|
|
|
## When Greedy Fails
|
|
|
|
We'll provide a few common examples of when greedy fails, so that you can avoid falling into obvious traps and wasting time getting wrong answers in contest.
|
|
|
|
### Coin Change
|
|
|
|
This problem gives several coin denominations, and asks for the minimum number of coins needed to make a certain value. Greedy algorithms can be used to solve this problem only in very specific cases (it can be proven that it works for the American as well as the Euro coin systems). However, it doesn't work in the general case. For example, let the coin denominations be $\{1, 3, 4\}$, and say the value we want is 6. The optimal solution is $\{3, 3\}$, which requires only two coins, but the greedy method of taking the highest possible valued coin that fits in the remaining denomination gives the solution $\{4, 1, 1\}$, which is incorrect.
|
|
|
|
### Knapsack
|
|
|
|
The knapsack problem gives a number of items, each having a weight and a value, and we want to choose a subset of these items. We are limited to a certain weight, and we want to maximize the value of the items that we take.
|
|
|
|
Let's take the following example, where we have a maximum capacity of 4:
|
|
|
|
<center>
|
|
|
|
| Item | Weight | Value | Value Per Weight |
|
|
|------|--------|-------|------------------|
|
|
| A | 3 | 18 | 6 |
|
|
| B | 2 | 10 | 5 |
|
|
| C | 2 | 10 | 5 |
|
|
|
|
</center>
|
|
|
|
If we use greedy based on highest value first, we choose item A and then we are done, as we don't have remaining weight to fit either of the other two. Using greedy based on value per weight again selects item A and then quits. However, the optimal solution is to select items B and C, as they combined have a higher value than item A alone. In fact, there is no working greedy solution. The solution to this problem uses **dynamic programming**, which is covered in gold.
|
|
|
|
|
|
## CSES Problems
|
|
|
|
<problems-list problems={metadata.problems.cses} />
|
|
|
|
## USACO Problems
|
|
|
|
<problems-list problems={metadata.problems.usaco} />
|
|
|
|
## Other Problems
|
|
|
|
<problems-list problems={metadata.problems.other} /> |