Backtracking and pruning
This commit is contained in:
parent
9de7221c10
commit
d3d26a99dc
323
luku05.tex
323
luku05.tex
|
@ -228,26 +228,28 @@ do {
|
||||||
} while (next_permutation(v.begin(),v.end()));
|
} while (next_permutation(v.begin(),v.end()));
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
\section{Peruuttava haku}
|
\section{Backtracking}
|
||||||
|
|
||||||
\index{peruuttava haku@peruuttava haku}
|
\index{backtracking}
|
||||||
|
|
||||||
\key{Peruuttava haku}
|
A \key{backtracking} algorithm
|
||||||
aloittaa ratkaisun etsimisen tyhjästä
|
begins from an empty solution
|
||||||
ja laajentaa ratkaisua askel kerrallaan.
|
and extends the solution step by step.
|
||||||
Joka askeleella haku haarautuu kaikkiin
|
At each step, the search branches
|
||||||
mahdollisiin suuntiin, joihin ratkaisua voi laajentaa.
|
to all possible directions how the solution
|
||||||
Haaran tutkimisen jälkeen haku peruuttaa takaisin
|
can be extended.
|
||||||
ja jatkaa muihin mahdollisiin suuntiin.
|
After processing one branch, the search
|
||||||
|
continues to other possible directions.
|
||||||
|
|
||||||
\index{kuningatarongelma}
|
\index{queen problem}
|
||||||
|
|
||||||
Tarkastellaan esimerkkinä \key{kuningatarongelmaa},
|
As an example, consider the \key{queen problem}
|
||||||
jossa laskettavana on,
|
where our task is to calculate the number
|
||||||
monellako tavalla $n \times n$ -shakkilaudalle
|
of ways we can place $n$ queens to
|
||||||
voidaan asettaa $n$ kuningatarta niin,
|
an $n \times n$ chessboard so that
|
||||||
että mitkään kaksi kuningatarta eivät uhkaa toisiaan.
|
no two queens attack each other.
|
||||||
Esimerkiksi kun $n=4$, mahdolliset ratkaisut ovat seuraavat:
|
For example, when $n=4$,
|
||||||
|
there are two possible solutions for the problem:
|
||||||
|
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\begin{tikzpicture}[scale=.65]
|
\begin{tikzpicture}[scale=.65]
|
||||||
|
@ -268,16 +270,16 @@ Esimerkiksi kun $n=4$, mahdolliset ratkaisut ovat seuraavat:
|
||||||
\end{tikzpicture}
|
\end{tikzpicture}
|
||||||
\end{center}
|
\end{center}
|
||||||
|
|
||||||
Tehtävän voi ratkaista peruuttavalla haulla
|
The problem can be solved using backtracking
|
||||||
muodostamalla ratkaisua rivi kerrallaan.
|
by placing queens to the board row by row.
|
||||||
Jokaisella rivillä täytyy valita yksi ruuduista,
|
More precisely, we should place exactly one queen
|
||||||
johon sijoitetaan kuningatar niin,
|
to each row so that no queen attacks
|
||||||
ettei se uhkaa mitään aiemmin lisättyä kuningatarta.
|
any of the queens placed before.
|
||||||
Ratkaisu on valmis, kun viimeisellekin
|
A solution is ready when we have placed all
|
||||||
riville on lisätty kuningatar.
|
$n$ queens to the board.
|
||||||
|
|
||||||
Esimerkiksi kun $n=4$, osa peruuttavan haun muodostamasta
|
For example, when $n=4$, the tree produced by
|
||||||
puusta näyttää seuraavalta:
|
the backtracking algorithm begins like this:
|
||||||
|
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\begin{tikzpicture}[scale=.55]
|
\begin{tikzpicture}[scale=.55]
|
||||||
|
@ -327,19 +329,16 @@ puusta näyttää seuraavalta:
|
||||||
\end{tikzpicture}
|
\end{tikzpicture}
|
||||||
\end{center}
|
\end{center}
|
||||||
|
|
||||||
Kuvan alimmalla tasolla kolme ensimmäistä osaratkaisua
|
At the bottom level, the three first subsolutions
|
||||||
eivät kelpaa, koska niissä kuningattaret uhkaavat
|
are not valid because the queens attack each other.
|
||||||
toisiaan.
|
However, the fourth subsolution is valid
|
||||||
Sen sijaan neljäs osaratkaisu kelpaa,
|
and it can be extended to a full solution by
|
||||||
ja sitä on mahdollista laajentaa loppuun asti
|
placing two more queens to the board.
|
||||||
kokonaiseksi ratkaisuksi
|
|
||||||
asettamalla vielä kaksi kuningatarta laudalle.
|
|
||||||
|
|
||||||
\begin{samepage}
|
\begin{samepage}
|
||||||
Seuraava koodi toteuttaa peruuttavan haun:
|
The following code implements the search:
|
||||||
|
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
void haku(int y) {
|
void search(int y) {
|
||||||
if (y == n) {
|
if (y == n) {
|
||||||
c++;
|
c++;
|
||||||
return;
|
return;
|
||||||
|
@ -347,32 +346,32 @@ void haku(int y) {
|
||||||
for (int x = 0; x < n; x++) {
|
for (int x = 0; x < n; x++) {
|
||||||
if (r1[x] || r2[x+y] || r3[x-y+n-1]) continue;
|
if (r1[x] || r2[x+y] || r3[x-y+n-1]) continue;
|
||||||
r1[x] = r2[x+y] = r3[x-y+n-1] = 1;
|
r1[x] = r2[x+y] = r3[x-y+n-1] = 1;
|
||||||
haku(y+1);
|
search(y+1);
|
||||||
r1[x] = r2[x+y] = r3[x-y+n-1] = 0;
|
r1[x] = r2[x+y] = r3[x-y+n-1] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
\end{samepage}
|
\end{samepage}
|
||||||
Haku alkaa kutsumalla funktiota \texttt{haku(0)}.
|
The search begins by calling \texttt{search(0)}.
|
||||||
Laudan koko on muuttujassa $n$,
|
The size of the board is in the variable $n$,
|
||||||
ja koodi laskee ratkaisuiden määrän
|
and the code calculates the number of solutions
|
||||||
muuttujaan $c$.
|
to the variable $c$.
|
||||||
|
|
||||||
Koodi olettaa, että laudan vaaka- ja pystyrivit
|
The code assumes that the rows and columns
|
||||||
on numeroitu 0:sta alkaen.
|
of the board are numbered from 0.
|
||||||
Funktio asettaa kuningattaren vaakariville $y$,
|
The function places a queen to row $y$
|
||||||
kun $0 \le y < n$.
|
when $0 \le y < n$.
|
||||||
Jos taas $y=n$, yksi ratkaisu on valmis
|
Finally, if $y=n$, one solution has been found
|
||||||
ja funktio kasvattaa muuttujaa $c$.
|
and the variable $c$ is increased by one.
|
||||||
|
|
||||||
Taulukko \texttt{r1} pitää kirjaa,
|
The array \texttt{r1} keeps track of the columns
|
||||||
millä laudan pystyriveillä on jo kuningatar.
|
that already contain a queen.
|
||||||
Vastaavasti taulukot \texttt{r2} ja \texttt{r3}
|
Similarly, the arrays \texttt{r2} and \texttt{r3}
|
||||||
pitävät kirjaa vinoriveistä.
|
keep track of the diagonals.
|
||||||
Tällaisille riveille ei voi laittaa enää toista
|
It is not allowed to add another queen to a
|
||||||
kuningatarta.
|
column or to a diagonal.
|
||||||
Esimerkiksi $4 \times 4$ -laudan tapauksessa
|
For example, the rows and the diagonals of
|
||||||
rivit on numeroitu seuraavasti:
|
the $4 \times 4$ board are numbered as follows:
|
||||||
|
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\begin{tikzpicture}[scale=.65]
|
\begin{tikzpicture}[scale=.65]
|
||||||
|
@ -439,36 +438,38 @@ rivit on numeroitu seuraavasti:
|
||||||
\end{tikzpicture}
|
\end{tikzpicture}
|
||||||
\end{center}
|
\end{center}
|
||||||
|
|
||||||
Koodin avulla selviää esimerkiksi,
|
Using the presented backtracking
|
||||||
että tapauksessa $n=8$ on 92 tapaa sijoittaa 8
|
algorithm, we can calculate that,
|
||||||
kuningatarta $8 \times 8$ -laudalle.
|
for example, there are 92 ways to place 8
|
||||||
Kun $n$ kasvaa, koodi hidastuu nopeasti,
|
queens to an $8 \times 8$ chessboard.
|
||||||
koska ratkaisujen määrä kasvaa räjähdysmäisesti.
|
When $n$ increases, the search quickly becomes slow
|
||||||
Tapauksen $n=16$ laskeminen vie jo noin minuutin
|
because the number of the solutions increases
|
||||||
nykyaikaisella tietokoneella (14772512 ratkaisua).
|
exponentially.
|
||||||
|
For example, calculating the ways to
|
||||||
|
place 16 queens to the $16 \times 16$
|
||||||
|
chessboard already takes about a minute
|
||||||
|
(there are 14772512 solutions).
|
||||||
|
|
||||||
\section{Haun optimointi}
|
\section{Pruning the search}
|
||||||
|
|
||||||
Peruuttavaa hakua on usein mahdollista tehostaa
|
A backtracking algorithm can often be optimized
|
||||||
erilaisten optimointien avulla.
|
by pruning the search tree.
|
||||||
Tavoitteena on lisätä hakuun ''älykkyyttä''
|
The idea is to add ''intelligence'' to the algorithm
|
||||||
niin, että haku pystyy havaitsemaan
|
so that it will notice as soon as possible
|
||||||
mahdollisimman aikaisin,
|
if is not possible to extend a subsolution into
|
||||||
jos muodosteilla oleva ratkaisu ei voi
|
a full solution.
|
||||||
johtaa kokonaiseen ratkaisuun.
|
This kind of optimization can have a tremendous
|
||||||
Tällaiset optimoinnit karsivat haaroja
|
effect on the efficiency of the search.
|
||||||
hakupuusta, millä voi olla suuri vaikutus
|
|
||||||
peruuttavan haun tehokkuuteen.
|
|
||||||
|
|
||||||
Tarkastellaan esimerkkinä tehtävää,
|
Let us consider a problem where
|
||||||
jossa laskettavana on reittien määrä
|
our task is to calculate the number of paths
|
||||||
$n \times n$ -ruudukon
|
in an $n \times n$ grid from the upper-left corner
|
||||||
vasemmasta yläkulmasta oikeaan alakulmaan,
|
to the lower-right corner so that each square
|
||||||
kun reitin aikana tulee käydä tarkalleen kerran
|
will be visited exactly once.
|
||||||
jokaisessa ruudussa.
|
For example, in the $7 \times 7$ grid,
|
||||||
Esimerkiksi $7 \times 7$ -ruudukossa on
|
there are 111712 possible paths from the
|
||||||
111712 mahdollista reittiä vasemmasta yläkulmasta
|
lower-right corner to the upper-right corner.
|
||||||
oikeaan alakulmaan, joista yksi on seuraava:
|
One of the paths is as follows:
|
||||||
|
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\begin{tikzpicture}[scale=.55]
|
\begin{tikzpicture}[scale=.55]
|
||||||
|
@ -486,38 +487,39 @@ oikeaan alakulmaan, joista yksi on seuraava:
|
||||||
\end{tikzpicture}
|
\end{tikzpicture}
|
||||||
\end{center}
|
\end{center}
|
||||||
|
|
||||||
Keskitymme seuraavaksi nimenomaan tapaukseen $7 \times 7$,
|
We will concentrate on the $7 \times 7$ case
|
||||||
koska se on laskennallisesti sopivan haastava.
|
because it is computationally suitable difficult.
|
||||||
Lähdemme liikkeelle suoraviivaisesta peruuttavaa hakua
|
We begin with a straightforward backtracking algorithm,
|
||||||
käyttävästä algoritmista
|
and then optimize it step by step using observations
|
||||||
ja teemme siihen pikkuhiljaa optimointeja,
|
how the search tree can be pruned.
|
||||||
jotka nopeuttavat hakua eri tavoin.
|
After each optimization, we measure the running time
|
||||||
Mittaamme jokaisen optimoinnin jälkeen
|
of the algorithm and the number of recursive calls,
|
||||||
algoritmin suoritusajan sekä rekursiokutsujen yhteismäärän,
|
so that we will clearly see the effect of each
|
||||||
jotta näemme selvästi, mikä vaikutus kullakin
|
optimization on the efficiency of the search.
|
||||||
optimoinnilla on haun tehokkuuteen.
|
|
||||||
|
|
||||||
\subsubsection{Perusalgoritmi}
|
\subsubsection{Basic algorithm}
|
||||||
|
|
||||||
Algoritmin ensimmäisessä versiossa ei ole mitään optimointeja,
|
The first version of the algorithm doesn't contain
|
||||||
vaan peruuttava haku käy läpi kaikki mahdolliset tavat
|
any optimizations. We simply use backtracking to generate
|
||||||
muodostaa reitti ruudukon vasemmasta yläkulmasta
|
all possible paths from the upper-left corner to
|
||||||
oikeaan alakulmaan.
|
the lower-right corner.
|
||||||
|
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item
|
\item
|
||||||
suoritusaika: 483 sekuntia
|
running time: 483 seconds
|
||||||
\item
|
\item
|
||||||
rekursiokutsuja: 76 miljardia
|
recursive calls: 76 billions
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
\subsubsection{Optimointi 1}
|
\subsubsection{Optimization 1}
|
||||||
|
|
||||||
Reitin ensimmäinen askel on joko alaspäin
|
The first step in a solution is either
|
||||||
tai oikealle. Tästä valinnasta seuraavat tilanteet
|
downward or to the right.
|
||||||
ovat symmetrisiä ruudukon lävistäjän suhteen.
|
There are always two paths that
|
||||||
Esimerkiksi seuraavat ratkaisut ovat
|
are symmetric
|
||||||
symmetrisiä keskenään:
|
about the diagonal of the grid
|
||||||
|
after the first step.
|
||||||
|
For example, the following paths are symmetric:
|
||||||
|
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\begin{tabular}{ccc}
|
\begin{tabular}{ccc}
|
||||||
|
@ -552,23 +554,24 @@ symmetrisiä keskenään:
|
||||||
\end{tabular}
|
\end{tabular}
|
||||||
\end{center}
|
\end{center}
|
||||||
|
|
||||||
Tämän ansiosta voimme tehdä päätöksen,
|
Thus, we can decide that the first step
|
||||||
että reitin ensimmäinen askel on alaspäin,
|
in the solution is always downward,
|
||||||
ja kertoa lopuksi reittien määrän 2:lla.
|
and finally multiply the number of the solutions by two.
|
||||||
|
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item
|
\item
|
||||||
suoritusaika: 244 sekuntia
|
running time: 244 seconds
|
||||||
\item
|
\item
|
||||||
rekursiokutsuja: 38 miljardia
|
recursive calls: 38 billions
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
\subsubsection{Optimointi 2}
|
\subsubsection{Optimization 2}
|
||||||
|
|
||||||
Jos reitti menee oikean alakulman ruutuun ennen kuin
|
If the path reaches the lower-right square
|
||||||
se on käynyt kaikissa muissa ruuduissa,
|
before it has visited all other squares of the grid,
|
||||||
siitä ei voi mitenkään enää saada kelvollista ratkaisua.
|
it is clear that
|
||||||
Näin on esimerkiksi seuraavassa tilanteessa:
|
it will not be possible to complete the solution.
|
||||||
|
An example of this is the following case:
|
||||||
|
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\begin{tikzpicture}[scale=.55]
|
\begin{tikzpicture}[scale=.55]
|
||||||
|
@ -582,23 +585,23 @@ Näin on esimerkiksi seuraavassa tilanteessa:
|
||||||
\end{scope}
|
\end{scope}
|
||||||
\end{tikzpicture}
|
\end{tikzpicture}
|
||||||
\end{center}
|
\end{center}
|
||||||
Niinpä voimme keskeyttää hakuhaaran heti,
|
Using this observation, we can terminate the search branch
|
||||||
jos tulemme oikean alakulman ruutuun liian aikaisin.
|
immediately if we reach the lower-right square too early.
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item
|
\item
|
||||||
suoritusaika: 119 sekuntia
|
running time: 119 seconds
|
||||||
\item
|
\item
|
||||||
rekursiokutsuja: 20 miljardia
|
recursive calls: 20 billions
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
\subsubsection{Optimointi 3}
|
\subsubsection{Optimization 3}
|
||||||
|
|
||||||
Jos reitti osuu seinään niin, että kummallakin puolella
|
If the path touches the wall so that there is
|
||||||
on ruutu, jossa reitti ei ole vielä käynyt,
|
an unvisited square at both sides,
|
||||||
ruudukko jakautuu kahteen osaan.
|
the grid splits into two parts.
|
||||||
Esimerkiksi seuraavassa tilanteessa
|
For example, in the following case
|
||||||
sekä vasemmalla että
|
both the left and the right squares
|
||||||
oikealla puolella on tyhjä ruutu:
|
are unvisited:
|
||||||
|
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\begin{tikzpicture}[scale=.55]
|
\begin{tikzpicture}[scale=.55]
|
||||||
|
@ -612,30 +615,31 @@ oikealla puolella on tyhjä ruutu:
|
||||||
\end{scope}
|
\end{scope}
|
||||||
\end{tikzpicture}
|
\end{tikzpicture}
|
||||||
\end{center}
|
\end{center}
|
||||||
Nyt ei ole enää mahdollista käydä kaikissa ruuduissa,
|
Now it will not be possible to visit every square,
|
||||||
joten voimme keskeyttää hakuhaaran.
|
so we can terminate the search branch.
|
||||||
Tämä optimointi on hyvin hyödyllinen:
|
This optimization is very useful:
|
||||||
|
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item
|
\item
|
||||||
suoritusaika: 1{,}8 sekuntia
|
running time: 1.8 seconds
|
||||||
\item
|
\item
|
||||||
rekursiokutsuja: 221 miljoonaa
|
recursive calls: 221 millions
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
\subsubsection{Optimointi 4}
|
\subsubsection{Optimization 4}
|
||||||
|
|
||||||
Äskeisen optimoinnin ideaa voi yleistää:
|
The idea of the previous optimization
|
||||||
ruudukko jakaantuu kahteen osaan,
|
can be generalized:
|
||||||
jos nykyisen ruudun ylä- ja alapuolella on
|
the grid splits into two parts
|
||||||
tyhjä ruutu sekä vasemmalla ja oikealla
|
if the top and bottom neighbors
|
||||||
puolella on seinä tai aiemmin käyty ruutu
|
of the current square are unvisited and
|
||||||
(tai päinvastoin).
|
the left and right neighbors are
|
||||||
|
wall or visited (or vice versa).
|
||||||
|
|
||||||
Esimerkiksi seuraavassa tilanteessa
|
For example, in the following case
|
||||||
nykyisen ruudun ylä- ja alapuolella on
|
the top and bottom neighbors are unvisited,
|
||||||
tyhjä ruutu eikä reitti voi enää edetä
|
so the path cannot visit all squares
|
||||||
molempiin ruutuihin:
|
in the grid anymore:
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\begin{tikzpicture}[scale=.55]
|
\begin{tikzpicture}[scale=.55]
|
||||||
\begin{scope}
|
\begin{scope}
|
||||||
|
@ -648,32 +652,33 @@ molempiin ruutuihin:
|
||||||
\end{scope}
|
\end{scope}
|
||||||
\end{tikzpicture}
|
\end{tikzpicture}
|
||||||
\end{center}
|
\end{center}
|
||||||
Haku tehostuu entisestään, kun keskeytämme
|
The search becomes even faster when we terminate
|
||||||
hakuhaaran kaikissa tällaisissa tapauksissa:
|
the search branch in all such cases:
|
||||||
|
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item
|
\item
|
||||||
suoritusaika: 0{,}6 sekuntia
|
running time: 0.6 seconds
|
||||||
\item
|
\item
|
||||||
rekursiokutsuja: 69 miljoonaa
|
recursive calls: 69 millions
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
~\\
|
~\\
|
||||||
Nyt on hyvä hetki lopettaa optimointi ja muistella,
|
Now it's a good moment to stop optimization
|
||||||
mistä lähdimme liikkeelle.
|
and remember our starting point.
|
||||||
Alkuperäinen algoritmi vei aikaa 483 sekuntia,
|
The running time of the original algorithm
|
||||||
ja nyt optimointien jälkeen algoritmi vie aikaa
|
was 483 seconds,
|
||||||
vain 0{,}6 sekuntia.
|
and now after the optimizations,
|
||||||
Optimointien ansiosta algoritmi nopeutui
|
the running time is only 0.6 seconds.
|
||||||
siis lähes 1000-kertaisesti.
|
Thus, the algorithm became nearly 1000 times
|
||||||
|
faster after the optimizations.
|
||||||
|
|
||||||
Tämä on yleinen ilmiö peruuttavassa haussa,
|
This is a usual phenomenon in backtracking
|
||||||
koska hakupuu on yleensä valtava ja
|
because the search tree is usually large
|
||||||
yksinkertainenkin optimointi voi karsia suuren
|
and even simple optimizations can prune
|
||||||
määrän haaroja hakupuusta.
|
a lot of branches in the tree.
|
||||||
Erityisen hyödyllisiä ovat optimoinnit,
|
Especially useful are optimizations that
|
||||||
jotka kohdistuvat hakupuun yläosaan,
|
occur at the top of the search tree because
|
||||||
koska ne karsivat eniten haaroja.
|
they can prune the search very efficiently.
|
||||||
|
|
||||||
\section{Puolivälihaku}
|
\section{Puolivälihaku}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue