First sections ready

This commit is contained in:
Antti H S Laaksonen 2016-12-29 19:59:39 +02:00
parent fb34683355
commit e64eb60b85
1 changed files with 158 additions and 164 deletions

View File

@ -1,162 +1,166 @@
\chapter{Time complexity} \chapter{Time complexity}
\index{aikavaativuus@aikavaativuus} \index{time complexity}
Kisakoodauksessa oleellinen asia on algoritmien tehokkuus. The efficiency of algorithms is important in competitive programming.
Yleensä on helppoa suunnitella algoritmi, Usually, it is easy to design an algorithm
joka ratkaisee tehtävän hitaasti, that solves the problem slowly,
mutta todellinen vaikeus piilee siinä, but the real challenge is to invent a
kuinka keksiä nopeasti toimiva algoritmi. fast algorithm.
Jos algoritmi on liian hidas, se tuottaa vain If an algorithm is too slow, it will get only
osan pisteistä tai ei pisteitä lainkaan. partial points or no points at all.
\key{Aikavaativuus} on kätevä tapa arvioida, The \key{time complexity} of an algorithm
kuinka nopeasti algoritmi toimii. estimates how much time the algorithm will use
Se esittää algoritmin tehokkuuden funktiona, for some input.
jonka parametrina on syötteen koko. The idea is to represent the efficiency
Aikavaativuuden avulla algoritmista voi päätellä ennen koodaamista, as an function whose parameter is the size of the input.
onko se riittävän tehokas tehtävän ratkaisuun. By calculating the time complexity,
we can estimate if the algorithm is good enough
without implementing it.
\section{Laskusäännöt} \section{Calculation rules}
Algoritmin aikavaativuus merkitään $O(\cdots)$, The time complexity of an algorithm
jossa kolmen pisteen tilalla is denoted $O(\cdots)$
on kaava, joka kuvaa algoritmin ajankäyttöä. where the three dots represent some
Yleensä muuttuja $n$ esittää syötteen kokoa. function.
Esimerkiksi jos algoritmin syötteenä on taulukko lukuja, Usually, the variable $n$ denotes
$n$ on lukujen määrä, the input size.
ja jos syötteenä on merkkijono, For example, if the input is an array of numbers,
$n$ on merkkijonon pituus. $n$ will be the size of the array,
and if the input is a string,
$n$ will be the length of the string.
\subsubsection*{Silmukat} \subsubsection*{Loops}
Algoritmin ajankäyttö johtuu usein The typical reason why an algorithm is slow is
pohjimmiltaan silmukoista, that it contains many loops that go through the input.
jotka käyvät syötettä läpi. The more nested loops the algorithm contains,
Mitä enemmän sisäkkäisiä silmukoita the slower it is.
algoritmissa on, sitä hitaampi se on. If there are $k$ nested loops,
Jos sisäkkäisiä silmukoita on $k$, the time complexity is $O(n^k)$.
aikavaativuus on $O(n^k)$.
Esimerkiksi seuraavan koodin aikavaativuus on $O(n)$: For example, the time complexity of the following code is $O(n)$:
\begin{lstlisting} \begin{lstlisting}
for (int i = 1; i <= n; i++) { for (int i = 1; i <= n; i++) {
// koodia // code
} }
\end{lstlisting} \end{lstlisting}
Vastaavasti seuraavan koodin aikavaativuus on $O(n^2)$: Correspondingly, the time complexity of the following code is $O(n^2)$:
\begin{lstlisting} \begin{lstlisting}
for (int i = 1; i <= n; i++) { for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) { for (int j = 1; j <= n; j++) {
// koodia // code
} }
} }
\end{lstlisting} \end{lstlisting}
\subsubsection*{Suuruusluokka} \subsubsection*{Order of magnitude}
Aikavaativuus ei kerro tarkasti, A time complexity doesn't tell the exact number
montako kertaa silmukan sisällä oleva koodi suoritetaan, of times the code inside a loop is executed,
vaan se kertoo vain suuruusluokan. but it only tells the order of magnitude.
Esimerkiksi seuraavissa esimerkeissä silmukat In the following examples, the code inside the loop
suoritetaan $3n$, $n+5$ ja $\lceil n/2 \rceil$ kertaa, is executed $3n$, $n+5$ and $\lceil n/2 \rceil$ times,
mutta kunkin koodin aikavaativuus on sama $O(n)$. but the time complexity of each code is $O(n)$.
\begin{lstlisting} \begin{lstlisting}
for (int i = 1; i <= 3*n; i++) { for (int i = 1; i <= 3*n; i++) {
// koodia // code
} }
\end{lstlisting} \end{lstlisting}
\begin{lstlisting} \begin{lstlisting}
for (int i = 1; i <= n+5; i++) { for (int i = 1; i <= n+5; i++) {
// koodia // code
} }
\end{lstlisting} \end{lstlisting}
\begin{lstlisting} \begin{lstlisting}
for (int i = 1; i <= n; i += 2) { for (int i = 1; i <= n; i += 2) {
// koodia // code
} }
\end{lstlisting} \end{lstlisting}
Seuraavan koodin aikavaativuus on puolestaan $O(n^2)$: As another example,
the time complexity of the following code is $O(n^2)$:
\begin{lstlisting} \begin{lstlisting}
for (int i = 1; i <= n; i++) { for (int i = 1; i <= n; i++) {
for (int j = i+1; j <= n; j++) { for (int j = i+1; j <= n; j++) {
// koodia // code
} }
} }
\end{lstlisting} \end{lstlisting}
\subsubsection*{Peräkkäisyys} \subsubsection*{Phases}
Jos koodissa on peräkkäisiä osia, If the code consists of consecutive phases,
kokonaisaikavaativuus on suurin yksittäisen the total time complexity is the largest
osan aikavaativuus. time complexity of a single phase.
Tämä johtuu siitä, että koodin hitain The reason for this is that the slowest
vaihe on yleensä koodin pullonkaula phase is usually the bottleneck of the code
ja muiden vaiheiden merkitys on pieni. and the other phases are not important.
Esimerkiksi seuraava koodi muodostuu For example, the following code consists
kolmesta osasta, of three phases with time complexities
joiden aikavaativuudet ovat $O(n)$, $O(n^2)$ ja $O(n)$. $O(n)$, $O(n^2)$ and $O(n)$.
Niinpä koodin aikavaativuus on $O(n^2)$. Thus, the total time complexity is $O(n^2)$.
\begin{lstlisting} \begin{lstlisting}
for (int i = 1; i <= n; i++) { for (int i = 1; i <= n; i++) {
// koodia // code
} }
for (int i = 1; i <= n; i++) { for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) { for (int j = 1; j <= n; j++) {
// koodia // code
} }
} }
for (int i = 1; i <= n; i++) { for (int i = 1; i <= n; i++) {
// koodia // code
} }
\end{lstlisting} \end{lstlisting}
\subsubsection*{Monta muuttujaa} \subsubsection*{Several variables}
Joskus syötteessä on monta muuttujaa, Sometimes the time complexity depends on
jotka vaikuttavat aikavaativuuteen. several variables.
Tällöin myös aikavaativuuden kaavassa esiintyy In this case, the formula for the time complexity
monta muuttujaa. contains several variables.
Esimerkiksi seuraavan koodin For example, the time complexity of the
aikavaativuus on $O(nm)$: following code is $O(nm)$:
\begin{lstlisting} \begin{lstlisting}
for (int i = 1; i <= n; i++) { for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) { for (int j = 1; j <= m; j++) {
// koodia // code
} }
} }
\end{lstlisting} \end{lstlisting}
\subsubsection*{Rekursio} \subsubsection*{Recursion}
Rekursiivisen funktion aikavaativuuden The time complexity of a recursive function
määrittää, montako kertaa funktiota kutsutaan yhteensä depends on the number of times the function is called
ja mikä on yksittäisen kutsun aikavaativuus. and the time complexity of a single call.
Kokonais\-aikavaativuus saadaan kertomalla The total time complexity is the product of
nämä arvot toisillaan. these values.
Tarkastellaan esimerkiksi seuraavaa funktiota: For example, consider the following function:
\begin{lstlisting} \begin{lstlisting}
void f(int n) { void f(int n) {
if (n == 1) return; if (n == 1) return;
f(n-1); f(n-1);
} }
\end{lstlisting} \end{lstlisting}
Kutsu $\texttt{f}(n)$ aiheuttaa yhteensä $n$ funktiokutsua, The call $\texttt{f}(n)$ causes $n$ function calls,
ja jokainen funktiokutsu vie aikaa $O(1)$, and the time complexity of each call is $O(1)$.
joten aikavaativuus on $O(n)$. Thus, the total time complexity is $O(n)$.
Tarkastellaan sitten seuraavaa funktiota: As another example, consider the following function:
\begin{lstlisting} \begin{lstlisting}
void g(int n) { void g(int n) {
if (n == 1) return; if (n == 1) return;
@ -164,11 +168,11 @@ void g(int n) {
g(n-1); g(n-1);
} }
\end{lstlisting} \end{lstlisting}
Tässä tapauksessa funktio haarautuu kahteen osaan, In this case the function branches into two parts.
joten kutsu $\texttt{g}(n)$ aiheuttaa kaikkiaan seuraavat kutsut: Thus, the call $\texttt{g}(n)$ causes the following calls:
\begin{center} \begin{center}
\begin{tabular}{rr} \begin{tabular}{rr}
kutsu & kerrat \\ call & amount \\
\hline \hline
$\texttt{g}(n)$ & 1 \\ $\texttt{g}(n)$ & 1 \\
$\texttt{g}(n-1)$ & 2 \\ $\texttt{g}(n-1)$ & 2 \\
@ -176,116 +180,106 @@ $\cdots$ & $\cdots$ \\
$\texttt{g}(1)$ & $2^{n-1}$ \\ $\texttt{g}(1)$ & $2^{n-1}$ \\
\end{tabular} \end{tabular}
\end{center} \end{center}
Tämän perusteella kutsun $\texttt{g}(n)$ aikavaativuus on Based on this, the time complexity is
\[1+2+4+\cdots+2^{n-1} = 2^n-1 = O(2^n).\] \[1+2+4+\cdots+2^{n-1} = 2^n-1 = O(2^n).\]
\section{Vaativuusluokkia} \section{Complexity classes}
\index{vaativuusluokka@vaativuusluokka} \index{complexity classes}
Usein esiintyviä vaativuusluokkia ovat seuraavat: Typical complexity classes are:
\begin{description} \begin{description}
\item[$O(1)$] \item[$O(1)$]
\index{vakioaikainen algoritmi@vakioaikainen algoritmi} \index{constant-time algorithm}
\key{Vakioaikainen} algoritmi The running time of a \key{constant-time} algorithm
käyttää saman verran aikaa minkä tahansa doesn't depend on the input size.
syötteen käsittelyyn, A typical constant-time algorithm is a direct
eli algoritmin nopeus ei riipu syötteen koosta. formula that calculates the answer.
Tyypillinen vakioaikainen algoritmi on suora kaava
vastauksen laskemiseen.
\item[$O(\log n)$] \item[$O(\log n)$]
\index{logaritminen algoritmi@logaritminen algoritmi} \index{logarithmic algorithm}
\key{Logaritminen} aikavaativuus A \key{logarithmic} algorithm often halves
syntyy usein siitä, että algoritmi the input size at each step.
puolittaa syötteen koon joka askeleella. The reason for this is that the logarithm
Logaritmi $\log_2 n$ näet ilmaisee, montako $\log_2 n$ equals the number of times
kertaa luku $n$ täytyy puolittaa, $n$ must be divided by 2 to produce 1.
ennen kuin tuloksena on 1.
\item[$O(\sqrt n)$] \item[$O(\sqrt n)$]
The running time of this kind of algorithm
Tällainen algoritmi sijoittuu is between $O(\log n)$ and $O(n)$.
aikavaativuuksien $O(\log n)$ ja $O(n)$ välimaastoon. A special feature of the square root is that
Neliöjuuren erityinen ominaisuus on, $\sqrt n = n/\sqrt n$, so the square root lies
että $\sqrt n = n/\sqrt n$, joten neliöjuuri ''in the middle'' of the input.
osuu tietyllä tavalla syötteen puoliväliin.
\item[$O(n)$] \item[$O(n)$]
\index{lineaarinen algoritmi@lineaarinen algoritmi} \index{linear algorithm}
\key{Lineaarinen} algoritmi käy syötteen läpi A \key{linear} algorithm goes through the input
kiinteän määrän kertoja. a constant number of times.
Tämä on usein paras mahdollinen aikavaativuus, This is often the best possible time complexity
koska yleensä syöte täytyy käydä because it is usually needed to access each
läpi ainakin kerran, input element at least once before
ennen kuin algoritmi voi ilmoittaa vastauksen. reporting the answer.
\item[$O(n \log n)$] \item[$O(n \log n)$]
This time complexity often means that the
Tämä aikavaativuus viittaa usein algorithm sorts the input
syötteen järjestämiseen, because the time complexity of efficient
koska tehokkaat järjestämisalgoritmit toimivat sorting algorithms is $O(n \log n)$.
ajassa $O(n \log n)$. Another possibility is that the algorithm
Toinen mahdollisuus on, että algoritmi uses a data structure where the time
käyttää tietorakennetta, complexity of each operation is $O(\log n)$.
jonka operaatiot ovat $O(\log n)$-aikaisia.
\item[$O(n^2)$] \item[$O(n^2)$]
\index{nelizllinen algoritmi@neliöllinen algoritmi} \index{quadratic algorithm}
\key{Neliöllinen} aikavaativuus voi syntyä A \key{quadratic} algorithm often contains
siitä, että algoritmissa on two nested loops.
kaksi sisäkkäistä silmukkaa. It is possible to go through all pairs of
Neliöllinen algoritmi voi käydä läpi kaikki input elements in $O(n^2)$ time.
tavat valita joukosta kaksi alkiota.
\item[$O(n^3)$] \item[$O(n^3)$]
\index{kuutiollinen algoritmi@kuutiollinen algoritmi} \index{cubic algorithm}
\key{Kuutiollinen} aikavaativuus voi syntyä siitä, A \key{cubic} algorithm often contains
että algoritmissa on three nested loops.
kolme sisäkkäistä silmukkaa. It is possible to go through all triplets of
Kuutiollinen algoritmi voi käydä läpi kaikki input elements in $O(n^3)$ time.
tavat valita joukosta kolme alkiota.
\item[$O(2^n)$] \item[$O(2^n)$]
This time complexity often means that
Tämä aikavaativuus tarkoittaa usein, the algorithm iterates through all
että algoritmi käy läpi kaikki syötteen osajoukot. subsets of the input elements.
Esimerkiksi joukon $\{1,2,3\}$ osajoukot ovat For example, the subsets of $\{1,2,3\}$ are
$\emptyset$, $\{1\}$, $\{2\}$, $\{3\}$, $\{1,2\}$, $\emptyset$, $\{1\}$, $\{2\}$, $\{3\}$, $\{1,2\}$,
$\{1,3\}$, $\{2,3\}$ sekä $\{1,2,3\}$. $\{1,3\}$, $\{2,3\}$ and $\{1,2,3\}$.
\item[$O(n!)$] \item[$O(n!)$]
This time complexity often means that
Tämä aikavaativuus voi syntyä siitä, the algorithm iterates trough all
että algoritmi käy läpi kaikki syötteen permutaatiot. permutations of the input elements.
Esimerkiksi joukon $\{1,2,3\}$ permutaatiot ovat For example, the permutations of $\{1,2,3\}$ are
$(1,2,3)$, $(1,3,2)$, $(2,1,3)$, $(2,3,1)$, $(1,2,3)$, $(1,3,2)$, $(2,1,3)$, $(2,3,1)$,
$(3,1,2)$ sekä $(3,2,1)$. $(3,1,2)$ and $(3,2,1)$.
\end{description} \end{description}
\index{polynominen algoritmi@polynominen algoritmi} \index{polynomial algorithm}
Algoritmi on \key{polynominen}, An algorithm is \key{polynomial}
jos sen aikavaativuus on korkeintaan $O(n^k)$, if its time complexity is at most $O(n^k)$
kun $k$ on vakio. where $k$ is a constant.
Edellä mainituista aikavaativuuksista All the above time complexities except
kaikki paitsi $O(2^n)$ ja $O(n!)$ $O(2^n)$ and $O(n!)$ are polynomial.
ovat polynomisia. In practice, the constant $k$ is usually small,
Käytännössä vakio $k$ on yleensä pieni, and therefore a polynomial time complexity
minkä ansiosta means that the algorithm is \emph{efficient}.
polynomisuus kuvastaa sitä,
että algoritmi on \emph{tehokas}.
\index{NP-vaikea ongelma}
Useimmat tässä kirjassa esitettävät algoritmit \index{NP-hard problem}
ovat polynomisia.
Silti on paljon ongelmia, joihin ei tunneta Most algorithms in this book are polynomial.
polynomista algoritmia eli ongelmaa ei osata Still, there are many important problems for which
ratkaista tehokkaasti. no polynomial algorithm is known, i.e.,
\key{NP-vaikeat} ongelmat ovat nobody knows how to solve the problem efficiently.
tärkeä joukko ongelmia, \key{NP-hard} problems are an important set
joihin ei tiedetä polynomista algoritmia. of problems for which no polynomial algorithm is known.
\section{Tehokkuuden arviointi} \section{Tehokkuuden arviointi}