Coin problem and scheduling

This commit is contained in:
Antti H S Laaksonen 2017-01-01 23:43:44 +02:00
parent 30bb6699b5
commit c0b6c97340
1 changed files with 151 additions and 161 deletions

View File

@ -1,131 +1,127 @@
\chapter{Greedy algorithms}
\index{ahne algoritmi@ahne algoritmi}
\index{greedy algorithm}
\key{Ahne algoritmi}
muodostaa tehtävän ratkaisun
tekemällä joka askeleella
sillä hetkellä parhaalta näyttävän valinnan.
Ahne algoritmi ei koskaan
peruuta tekemiään valintoja vaan
muodostaa ratkaisun suoraan valmiiksi.
Tämän ansiosta ahneet algoritmit ovat
yleensä hyvin tehokkaita.
A \key{greedy algorithm}
constructs a solution for a problem
by always making a choice that looks
the best at the moment.
A greedy algorithm never takes back
its choices, but directly constructs
the final solution.
For this reason, greedy algorithms
are usually very efficient.
Vaikeutena ahneissa algoritmeissa on
keksiä toimiva ahne strategia,
joka tuottaa aina optimaalisen ratkaisun tehtävään.
Ahneen algoritmin tulee olla sellainen,
että kulloinkin parhaalta näyttävät valinnat
tuottavat myös parhaan kokonaisuuden.
Tämän perusteleminen on usein hankalaa.
The difficulty in designing a greedy algorithm
is to invent a greedy strategy
that always produces an optimal solution
for the problem.
The locally optimal choices in a greedy
algorithm should also be globally optimal.
It's often difficult to argue why
a greedy algorithm works.
\section{Kolikkotehtävä}
\section{Coin problem}
Aloitamme ahneisiin algoritmeihin tutustumisen
tehtävästä, jossa muodostettavana on
rahamäärä $x$ kolikoista.
Kolikoiden arvot ovat $\{c_1,c_2,\ldots,c_k\}$,
ja jokaista kolikkoa on saatavilla rajattomasti.
Tehtävänä on selvittää, mikä on pienin määrä
kolikoita, joilla rahamäärän voi muodostaa.
As the first example, we consider a problem
where we are given a set of coin values
and our task is to form a sum of money
using the coins.
The values of the coins are
$\{c_1,c_2,\ldots,c_k\}$,
and each coin can be used as many times we want.
What is the minimum number of coins needed?
Esimerkiksi jos muodostettava
rahamäärä on 520
ja kolikot ovat eurokolikot eli sentteinä
\[\{1,2,5,10,20,50,100,200\},\]
niin kolikoita tarvitaan vähintään 4.
Tämä on mahdollista valitsemalla kolikot
$200+200+100+20$, joiden summa on 520.
For example, if the coins are euro coins (in cents)
\[\{1,2,5,10,20,50,100,200\}\]
and the sum of money is 520,
we need at least four coins.
The optimal solution is to select coins
$200+200+100+20$ whose sum is 520.
\subsubsection{Ahne algoritmi}
\subsubsection{Greedy algorithm}
Luonteva ahne algoritmi tehtävään
on poistaa rahamäärästä aina mahdollisimman
suuri kolikko, kunnes rahamäärä on 0.
Tämä algoritmi toimii esimerkissä,
koska rahamäärästä 520
poistetaan ensin kahdesti 200, sitten 100
ja lopuksi 20.
Mutta toimiiko ahne algoritmi aina oikein?
A natural greedy algorithm for the problem
is to always select the largest possible coin,
until we have constructed the required sum of money.
This algorithm works in the example case,
because we first select two 200 cent coins,
then one 100 cent coin and finally one 20 cent coin.
But does this algorithm always work?
Osoittautuu, että eurokolikoiden tapauksessa
ahne algoritmi \emph{toimii} aina oikein,
eli se tuottaa aina ratkaisun,
jossa on pienin määrä kolikoita.
Algoritmin toimivuuden voi perustella
seuraavasti:
It turns out that, for the set of euro coins,
the greedy algorithm \emph{always} works, i.e.,
it always produces a solution with the fewest
possible number of coins.
The correctness of the algorithm can be
argued as follows:
Kutakin kolikkoa 1, 5, 10, 50 ja 100
on optimiratkaisussa enintään yksi.
Tämä johtuu siitä, että jos
ratkaisussa olisi kaksi tällaista kolikkoa,
saman ratkaisun voisi muodostaa
käyttäen vähemmän kolikoita.
Esimerkiksi jos ratkaisussa on
kolikot $5+5$, ne voi korvata kolikolla 10.
Each coin 1, 5, 10, 50 and 100 appears
at most once in the optimal solution.
The reason for this is that if the
solution would contain two such coins,
we could replace them by one coin and
obtain a better solution.
For example, if the solution would contain
coins $5+5$, we could replace them by coin $10$.
Vastaavasti kumpaakin kolikkoa 2 ja 20
on optimiratkaisussa enintään kaksi,
koska kolikot $2+2+2$ voi korvata kolikoilla $5+1$
ja kolikot $20+20+20$ voi korvata kolikoilla $50+10$.
Lisäksi ratkaisussa ei voi olla yhdistelmiä
$2+2+1$ ja $20+20+10$,
koska ne voi korvata kolikoilla 5 ja 50.
In the same way, both coins 2 and 20 can appear
at most twice in the optimal solution
because, we could replace
coins $2+2+2$ by coins $5+1$ and
coins $20+20+20$ by coins $50+10$.
Moreover, the optimal solution can't contain
coins $2+2+1$ or $20+20+10$
because we would replace them by coins $5$ and $50$.
Näiden havaintojen perusteella
jokaiselle kolikolle $x$ pätee,
että $x$:ää pienemmistä kolikoista
ei ole mahdollista saada aikaan summaa
$x$ tai suurempaa summaa optimaalisesti.
Esimerkiksi jos $x=100$, pienemmistä kolikoista
saa korkeintaan summan $50+20+20+5+2+2=99$.
Niinpä ahne algoritmi,
joka valitsee aina suurimman kolikon,
tuottaa optimiratkaisun.
Using these observations,
we can show for each coin $x$ that
it is not possible to optimally construct
sum $x$ or any larger sum by only using coins
that are smaller than $x$.
For example, if $x=100$, the largest optimal
sum using the smaller coins is $5+20+20+5+2+2=99$.
Thus, the greedy algorithm that always selects
the largest coin produces the optimal solution.
Kuten tästä esimerkistä huomaa,
ahneen algoritmin toimivuuden perusteleminen
voi olla työlästä,
vaikka kyseessä olisi yksinkertainen algoritmi.
This example shows that it can be difficult
to argue why a greedy algorithm works,
even if the algorithm itself is simple.
\subsubsection{Yleinen tapaus}
\subsubsection{General case}
Yleisessä tapauksessa kolikot voivat olla mitä tahansa.
Tällöin suurimman kolikon valitseva ahne algoritmi
\emph{ei} välttämättä tuota optimiratkaisua.
In the general case, the coin set can contain any coins
and the greedy algorithm \emph{not} necessarily produces
an optimal solution.
Jos ahne algoritmi ei toimi, tämän voi osoittaa
näyttämällä vastaesimerkin, jossa algoritmi
antaa väärän vastauksen.
Tässä tehtävässä vastaesimerkki on helppoa keksiä:
jos kolikot ovat $\{1,3,4\}$ ja muodostettava
rahamäärä on 6, ahne algoritmi tuottaa ratkaisun
$4+1+1$, kun taas optimiratkaisu on $3+3$.
We can prove that a greedy algorithm doesn't work
by showing a counterexample
where the algorithm gives a wrong answer.
In this problem it's easy to find a counterexample:
if the coins are $\{1,3,4\}$ and the sum of money
is 6, the greedy algorithm produces the solution
$4+1+1$, while the optimal solution is $3+3$.
Yleisessä tapauksessa kolikkotehtävän ratkaisuun
ei tunneta ahnetta algoritmia,
mutta palaamme tehtävään seuraavassa luvussa.
Tehtävään on nimittäin olemassa dynaamista
ohjelmointia käyttävä algoritmi,
joka tuottaa optimiratkaisun
millä tahansa kolikoilla ja rahamäärällä.
We don't know if the general coin problem
can be solved using any greedy algorithm.
However, we will revisit the problem in the next chapter
because the general problem can be solved using a dynamic
programming algorithm that always gives the
correct answer.
\section{Aikataulutus}
\section{Scheduling}
Monet aikataulutukseen liittyvät ongelmat
ratkeavat ahneesti.
Klassinen ongelma on seuraava:
Annettuna on $n$ tapahtumaa,
jotka alkavat ja päättyvät tiettyinä hetkinä.
Tehtäväsi on suunnitella aikataulu,
jota seuraamalla pystyt osallistumaan
mahdollisimman moneen tapahtumaan.
Et voi osallistua tapahtumaan vain osittain.
Esimerkiksi tilanteessa
Many scheduling problems can be solved
using a greedy strategy.
A classic problem is as follows:
Given $n$ events with their starting and ending
times, our task is to plan a schedule
so that we can join as many events as possible.
It's not possible to join an event partially.
For example, consider the following events:
\begin{center}
\begin{tabular}{lll}
tapahtuma & alkuaika & loppuaika \\
event & starting time & ending time \\
\hline
$A$ & 1 & 3 \\
$B$ & 2 & 5 \\
@ -133,10 +129,9 @@ $C$ & 3 & 9 \\
$D$ & 6 & 8 \\
\end{tabular}
\end{center}
on mahdollista osallistua korkeintaan
kahteen tapahtumaan.
Yksi mahdollisuus on osallistua tapahtumiin
$B$ ja $D$ seuraavasti:
In this case the maximum number of events is two.
For example, we can join events $B$ and $D$
as follows:
\begin{center}
\begin{tikzpicture}[scale=.4]
\begin{scope}
@ -152,16 +147,15 @@ $B$ ja $D$ seuraavasti:
\end{tikzpicture}
\end{center}
Tehtävän ratkaisuun on mahdollista
keksiä useita ahneita algoritmeja,
mutta mikä niistä toimii kaikissa tapauksissa?
It is possible to invent several greedy algorithms
for the problem, but which of them works in every case?
\subsubsection*{Algoritmi 1}
\subsubsection*{Algorithm 1}
Ensimmäinen idea on valita ratkaisuun
mahdollisimman \emph{lyhyitä} tapahtumia.
Esimerkin tapauksessa tällainen
algoritmi valitsee seuraavat tapahtumat:
The first idea is to select as \emph{short}
events as possible.
In the example case this algorithm
selects the following events:
\begin{center}
\begin{tikzpicture}[scale=.4]
\begin{scope}
@ -177,9 +171,9 @@ algoritmi valitsee seuraavat tapahtumat:
\end{tikzpicture}
\end{center}
Lyhimpien tapahtumien valinta ei ole kuitenkaan
aina toimiva strategia,
vaan algoritmi epäonnistuu esimerkiksi seuraavassa tilanteessa:
However, choosing short events is not always
a correct strategy but the algorithm fails,
for example, in the following case:
\begin{center}
\begin{tikzpicture}[scale=.4]
\begin{scope}
@ -189,16 +183,14 @@ vaan algoritmi epäonnistuu esimerkiksi seuraavassa tilanteessa:
\end{scope}
\end{tikzpicture}
\end{center}
Kun lyhyt tapahtuma valitaan mukaan,
on mahdollista osallistua vain yhteen tapahtumaan.
Kuitenkin valitsemalla pitkät tapahtumat
olisi mahdollista osallistua kahteen tapahtumaan.
If we select the short event, we can only select one event.
However, it would be possible to select both the long events.
\subsubsection*{Algoritmi 2}
\subsubsection*{Algorithm 2}
Toinen idea on valita aina seuraavaksi tapahtuma,
joka \emph{alkaa} mahdollisimman aikaisin.
Tämä algoritmi valitsee esimerkissä seuraavat tapahtumat:
Another idea is to always select the next possible
event that \emph{begins} as \emph{early} as possible.
This algorithm selects the following events:
\begin{center}
\begin{tikzpicture}[scale=.4]
\begin{scope}
@ -214,9 +206,10 @@ Tämä algoritmi valitsee esimerkissä seuraavat tapahtumat:
\end{tikzpicture}
\end{center}
Tämäkään algoritmi ei kuitenkaan toimi aina.
Esimerkiksi seuraavassa tilanteessa
algoritmi valitsee vain yhden tapahtuman:
However, we can find a counterexample for this
algorithm, too.
For example, in the following case,
the algorithm selects only one event:
\begin{center}
\begin{tikzpicture}[scale=.4]
\begin{scope}
@ -226,18 +219,16 @@ algoritmi valitsee vain yhden tapahtuman:
\end{scope}
\end{tikzpicture}
\end{center}
Kun ensimmäisenä alkava tapahtuma
valitaan mukaan, mitään muuta tapahtumaa
ei ole mahdollista valita.
Kuitenkin olisi mahdollista osallistua
kahteen tapahtumaan valitsemalla
kaksi myöhempää tapahtumaa.
If we select the first event, it is not possible
to select any other events.
However, it would be possible to join the
other two events.
\subsubsection*{Algoritmi 3}
\subsubsection*{Algorithm 3}
Kolmas idea on valita aina seuraavaksi tapahtuma,
joka \emph{päättyy} mahdollisimman aikaisin.
Tämä algoritmi valitsee esimerkissä seuraavat tapahtumat:
The third idea is to always select the next
possible event that \emph{ends} as \emph{early} as possible.
This algorithm selects the following events:
\begin{center}
\begin{tikzpicture}[scale=.4]
\begin{scope}
@ -253,26 +244,25 @@ Tämä algoritmi valitsee esimerkissä seuraavat tapahtumat:
\end{tikzpicture}
\end{center}
Osoittautuu, että tämä ahne algoritmi
tuottaa \textit{aina} optimiratkaisun.
Algoritmi toimii, koska on aina kokonaisuuden
kannalta optimaalista valita
ensimmäiseksi tapahtumaksi
mahdollisimman aikaisin päättyvä tapahtuma.
Tämän jälkeen on taas optimaalista
valita seuraava aikatauluun sopiva
mahdollisimman aikaisin
päättyvä tapahtua, jne.
It turns out that this algorithm
\emph{always} produces an optimal solution.
The algorithm works because
regarding the final solution, it is
optimal to select an event that
ends as soon as possible.
Then it is optimal to select
the next event using the same strategy, etc.
Yksi tapa perustella valintaa on miettiä,
mitä tapahtuu, jos ensimmäiseksi tapahtumaksi
valitaan jokin muu kuin mahdollisimman
aikaisin päättyvä tapahtuma.
Tällainen valinta ei ole koskaan parempi,
koska myöhemmin päättyvän tapahtuman
jälkeen on joko yhtä paljon tai vähemmän
mahdollisuuksia valita seuraavia tapahtumia
kuin aiemmin päättyvän tapahtuman jälkeen.
One way to justify the choice is to think
what happens if we first select some event
that ends later than the event that ends
as soon as possible.
This can never be a better choice
because after an event that ends later,
we will have at most an equal number of
possibilities to select for the next events,
compared to the strategy that we select the
event that ends as soon as possible.
\section{Tehtävät ja deadlinet}