Subsets and permutations

This commit is contained in:
Antti H S Laaksonen 2017-01-01 19:38:49 +02:00
parent 9bdb67701a
commit 9de7221c10
1 changed files with 120 additions and 123 deletions

View File

@ -1,102 +1,102 @@
\chapter{Complete search} \chapter{Complete search}
\key{Täydellinen haku} \key{Compelete search}
on yleispätevä tapa ratkaista is a general method that can be used
lähes mikä tahansa ohjelmointitehtävä. for solving almost any algorithm problem.
Ideana on käydä läpi raa'alla voimalla kaikki The idea is to generate all possible
mahdolliset tehtävän ratkaisut ja tehtävästä riippuen solutions for the problem using brute force,
valita paras ratkaisu and select the best solution or count the
tai laskea ratkaisuiden yhteismäärä. number of solutions, depending on the problem.
Täydellinen haku on hyvä menetelmä, jos kaikki
ratkaisut ehtii käydä läpi,
koska haku on yleensä suoraviivainen toteuttaa
ja se antaa varmasti oikean vastauksen.
Jos täydellinen haku on liian hidas,
seuraavien lukujen ahneet algoritmit tai
dynaaminen ohjelmointi voivat soveltua
tehtävään.
\section{Osajoukkojen läpikäynti} Complete search is a good technique
if it is feasible to go through all the solutions,
because the search is usually easy to implement
and it always gives the correct answer.
If complete search is too slow,
greedy algorithms or dynamic programming,
presented in the next chapters,
may be used.
\index{osajoukko@osajoukko} \section{Generating subsets}
Aloitamme tapauksesta, jossa tehtävän \index{subset}
mahdollisia ratkaisuja ovat
$n$-alkioisen joukon osajoukot.
Tällöin täydellisen haun tulee
käydä läpi kaikki joukon osa\-joukot,
joita on yhteensä $2^n$ kappaletta.
Käymme läpi kaksi menetelmää
tällaisen haun toteuttamiseen.
\subsubsection{Menetelmä 1} We first consider the case where
the possible solutions for the problem
are the subsets of a set of $n$ elements.
In this case, a complete search algorithm
has to generate
all $2^n$ subsets of the set.
Kätevä tapa käydä läpi \subsubsection{Method 1}
kaikki joukon osajoukot on
käyttää rekursiota. An elegant way to go through all subsets
Seuraava funktio \texttt{haku} muodostaa of a set is to use recursion.
joukon $\{1,2,\ldots,n\}$ osajoukot. The following function \texttt{gen}
Funktio pitää yllä vektoria \texttt{v}, generates the subsets of the set
johon se kokoaa osajoukossa olevat luvut. $\{1,2,\ldots,n\}$.
Osajoukkojen muodostaminen alkaa The function maintains a vector \texttt{v}
tekemällä funktiokutsu \texttt{haku(1)}. that will contain the elements in the subset.
The generation of the subsets
begins when the function
is called with parameter $1$.
\begin{lstlisting} \begin{lstlisting}
void haku(int k) { void gen(int k) {
if (k == n+1) { if (k == n+1) {
// käsittele osajoukko v // process subset v
} else { } else {
haku(k+1); gen(k+1);
v.push_back(k); v.push_back(k);
haku(k+1); gen(k+1);
v.pop_back(); v.pop_back();
} }
} }
\end{lstlisting} \end{lstlisting}
Funktion parametri $k$ on luku, The parameter $k$ is the number that is the next
joka on ehdolla lisättäväksi osajoukkoon seuraavaksi. candidate to be included in the subset.
Joka kutsulla funktio haarautuu kahteen tapaukseen: The function branches to two cases:
joko luku $k$ lisätään tai ei lisätä osajoukkoon. either $k$ is included or it is not included in the subset.
Aina kun $k=n+1$, kaikki luvut on käyty läpi Finally, when $k=n+1$, a decision has been made for
ja yksi osajoukko on muodostettu. all the numbers and one subset has been generated.
For example, when $n=3$, the function calls
create a tree illustrated below.
At each call, the left branch doesn't include
the number and the right branch includes the number
in the subset.
Esimerkiksi kun $n=3$, funktiokutsut
muodostavat seuraavan kuvan mukaisen puun.
Joka kutsussa
vasen haara jättää luvun pois osajoukosta
ja oikea haara lisää sen osajoukkoon.
\begin{center} \begin{center}
\begin{tikzpicture}[scale=.45] \begin{tikzpicture}[scale=.45]
\begin{scope} \begin{scope}
\small \small
\node at (0,0) {$\texttt{haku}(1)$}; \node at (0,0) {$\texttt{gen}(1)$};
\node at (-8,-4) {$\texttt{haku}(2)$}; \node at (-8,-4) {$\texttt{gen}(2)$};
\node at (8,-4) {$\texttt{haku}(2)$}; \node at (8,-4) {$\texttt{gen}(2)$};
\path[draw,thick,->] (0,0-0.5) -- (-8,-4+0.5); \path[draw,thick,->] (0,0-0.5) -- (-8,-4+0.5);
\path[draw,thick,->] (0,0-0.5) -- (8,-4+0.5); \path[draw,thick,->] (0,0-0.5) -- (8,-4+0.5);
\node at (-12,-8) {$\texttt{haku}(3)$}; \node at (-12,-8) {$\texttt{gen}(3)$};
\node at (-4,-8) {$\texttt{haku}(3)$}; \node at (-4,-8) {$\texttt{gen}(3)$};
\node at (4,-8) {$\texttt{haku}(3)$}; \node at (4,-8) {$\texttt{gen}(3)$};
\node at (12,-8) {$\texttt{haku}(3)$}; \node at (12,-8) {$\texttt{gen}(3)$};
\path[draw,thick,->] (-8,-4-0.5) -- (-12,-8+0.5); \path[draw,thick,->] (-8,-4-0.5) -- (-12,-8+0.5);
\path[draw,thick,->] (-8,-4-0.5) -- (-4,-8+0.5); \path[draw,thick,->] (-8,-4-0.5) -- (-4,-8+0.5);
\path[draw,thick,->] (8,-4-0.5) -- (4,-8+0.5); \path[draw,thick,->] (8,-4-0.5) -- (4,-8+0.5);
\path[draw,thick,->] (8,-4-0.5) -- (12,-8+0.5); \path[draw,thick,->] (8,-4-0.5) -- (12,-8+0.5);
\node at (-14,-12) {$\texttt{haku}(4)$}; \node at (-14,-12) {$\texttt{gen}(4)$};
\node at (-10,-12) {$\texttt{haku}(4)$}; \node at (-10,-12) {$\texttt{gen}(4)$};
\node at (-6,-12) {$\texttt{haku}(4)$}; \node at (-6,-12) {$\texttt{gen}(4)$};
\node at (-2,-12) {$\texttt{haku}(4)$}; \node at (-2,-12) {$\texttt{gen}(4)$};
\node at (2,-12) {$\texttt{haku}(4)$}; \node at (2,-12) {$\texttt{gen}(4)$};
\node at (6,-12) {$\texttt{haku}(4)$}; \node at (6,-12) {$\texttt{gen}(4)$};
\node at (10,-12) {$\texttt{haku}(4)$}; \node at (10,-12) {$\texttt{gen}(4)$};
\node at (14,-12) {$\texttt{haku}(4)$}; \node at (14,-12) {$\texttt{gen}(4)$};
\node at (-14,-13.5) {$\emptyset$}; \node at (-14,-13.5) {$\emptyset$};
\node at (-10,-13.5) {$\{3\}$}; \node at (-10,-13.5) {$\{3\}$};
@ -120,36 +120,36 @@ ja oikea haara lisää sen osajoukkoon.
\end{tikzpicture} \end{tikzpicture}
\end{center} \end{center}
\subsubsection{Menetelmä 2} \subsubsection{Method 2}
Toinen tapa käydä osajoukot läpi on hyödyntää kokonaislukujen Another way to generate the subsets is to exploit
bittiesitystä. Jokainen $n$ alkion osajoukko the bit representation of integers.
voidaan esittää $n$ bitin jonona, Each subset of a set of $n$ elements
joka taas vastaa lukua väliltä $0 \ldots 2^n-1$. can be represented as a sequence of $n$ bits,
Bittiesityksen ykkösbitit ilmaisevat, which corresponds to an integer between $0 \ldots 2^n-1$.
mitkä joukon alkiot on valittu osajoukkoon. The ones in the bit representation indicate
which elements of the set are included in the subset.
Tavallinen käytäntö on tulkita kokonaisluvun The usual interpretation is that element $k$
bittiesitys osajoukkona niin, is included in the subset if $k$th bit from the
että alkio $k$ kuuluu osajoukkoon, end of the bit sequence is one.
jos lopusta lukien $k$. bitti on 1. For example, the bit representation of 25
Esimerkiksi luvun 25 bittiesitys on 11001, is 11001 that corresponds to the subset $\{1,4,5\}$.
mikä vastaa osajoukkoa $\{1,4,5\}$.
Seuraava koodi käy läpi $n$ alkion joukon The following iterates through all subsets
osajoukkojen bittiesitykset: of a set of $n$ elements
\begin{lstlisting} \begin{lstlisting}
for (int b = 0; b < (1<<n); b++) { for (int b = 0; b < (1<<n); b++) {
// käsittele osajoukko b // process subset b
} }
\end{lstlisting} \end{lstlisting}
Seuraava koodi muodostaa jokaisen osajoukon The following code converts each bit
kohdalla vektorin \texttt{v}, representation to a vector \texttt{v}
joka sisältää osajoukossa olevat luvut. that contains the elements in the subset.
Ne saadaan selville tutkimalla, mitkä bitit ovat This can be done by checking which bits
ykkösiä osajoukon bittiesityksessä. are one in the bit representation.
\begin{lstlisting} \begin{lstlisting}
for (int b = 0; b < (1<<n); b++) { for (int b = 0; b < (1<<n); b++) {
@ -160,33 +160,30 @@ for (int b = 0; b < (1<<n); b++) {
} }
\end{lstlisting} \end{lstlisting}
\section{Permutaatioiden läpikäynti} \section{Generating permutations}
\index{permutaatio@permutaatio} \index{permutation}
Toinen usein esiintyvä tilanne on, Another common situation is that the solutions
että tehtävän ratkaisut ovat $n$-alkioisen for the problem are permutations of a
joukon permutaatioita, set of $n$ elements.
jolloin täydellisen haun tulee In this case, a complete search algorithm has to
käydä läpi $n!$ mahdollista permutaatiota. generate $n!$ possible permutations.
Myös tässä tapauksessa on kaksi luontevaa
menetelmää täydellisen haun toteuttamiseen.
\subsubsection{Menetelmä 1} \subsubsection{Method 1}
Osajoukkojen tavoin permutaatioita voi muodostaa Like subsets, permutations can be generated
rekursiivisesti. using recursion.
Seuraava funktio \texttt{haku} käy läpi The following function \texttt{gen} iterates
joukon $\{1,2,\ldots,n\}$ permutaatiot. through the permutations of the set $\{1,2,\ldots,n\}$.
Funktio muodostaa kunkin permutaation The function uses the vector \texttt{v}
vuorollaan vektoriin \texttt{v}. for storing the permutations, and the generation
Permutaatioiden muodostus alkaa kutsumalla begins by calling the function without parameters.
funktiota ilman parametreja.
\begin{lstlisting} \begin{lstlisting}
void haku() { void haku() {
if (v.size() == n) { if (v.size() == n) {
// käsittele permutaatio v // process permutation v
} else { } else {
for (int i = 1; i <= n; i++) { for (int i = 1; i <= n; i++) {
if (p[i]) continue; if (p[i]) continue;
@ -200,26 +197,26 @@ void haku() {
} }
\end{lstlisting} \end{lstlisting}
Funktion jokainen kutsu lisää uuden Each function call adds a new element to
luvun permutaatioon vektoriin \texttt{v}. the permutation in the vector \texttt{v}.
Taulukko \texttt{p} kertoo, mitkä luvut on jo The array \texttt{p} indicates which
valittu permutaatioon. elements are already included in the permutation.
Jos $\texttt{p}[k]=0$, luku $k$ ei ole mukana, If $\texttt{p}[k]=0$, element $k$ is not included,
ja jos $\texttt{p}[k]=1$, luku $k$ on mukana. and if $\texttt{p}[k]=1$, element $k$ is included.
Jos vektorin \texttt{v} koko on sama kuin If the size of the vector equals the size of the set,
joukon koko $n$, permutaatio on tullut valmiiksi. a permutation has been generated.
\subsubsection{Menetelmä 2} \subsubsection{Method 2}
\index{next\_permutation@\texttt{next\_permutation}} \index{next\_permutation@\texttt{next\_permutation}}
Toinen ratkaisu on aloittaa permutaatiosta Another method is to begin from permutation
$\{1,2,\ldots,n\}$ ja muodostaa joka askeleella $\{1,2,\ldots,n\}$ and at each step generate the
järjestyksessä seuraava permutaatio. next permutation in increasing order.
C++:n standardikirjastossa on funktio The C++ standard library contains the function
\texttt{next\_permutation}, joka tekee tämän muunnoksen. \texttt{next\_permutation} that can be used for this.
Seuraava koodi käy läpi joukon $\{1,2,\ldots,n\}$ The following code generates the permutations
permutaatiot funktion avulla: of the set $\{1,2,\ldots,n\}$ using the function:
\begin{lstlisting} \begin{lstlisting}
vector<int> v; vector<int> v;
@ -227,7 +224,7 @@ for (int i = 1; i <= n; i++) {
v.push_back(i); v.push_back(i);
} }
do { do {
// käsittele permutaatio v // process permutation v
} while (next_permutation(v.begin(),v.end())); } while (next_permutation(v.begin(),v.end()));
\end{lstlisting} \end{lstlisting}