First sections ready
This commit is contained in:
parent
fb34683355
commit
e64eb60b85
322
luku02.tex
322
luku02.tex
|
@ -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}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue