Chapter 4 first version
This commit is contained in:
parent
03c12213d9
commit
46f2aa6b71
267
luku04.tex
267
luku04.tex
|
@ -538,20 +538,18 @@ cout << (a^b) << "\n"; // 1001101110
|
|||
|
||||
\subsubsection{Pakka}
|
||||
|
||||
\index{pakka@pakka}
|
||||
\index{deque}
|
||||
\index{deque@\texttt{deque}}
|
||||
|
||||
\key{Pakka} (\texttt{deque}) on dynaaminen taulukko,
|
||||
jonka kokoa pystyy muuttamaan tehokkaasti
|
||||
sekä alku- että loppupäässä.
|
||||
Pakka sisältää vektorin tavoin
|
||||
funktiot \texttt{push\_back}
|
||||
ja \texttt{pop\_back}, mutta siinä on lisäksi myös funktiot
|
||||
\texttt{push\_front} ja \texttt{pop\_front},
|
||||
jotka käsittelevät taulukon alkua.
|
||||
|
||||
Seuraava koodi esittelee pakan käyttämistä:
|
||||
A \key{deque} (\texttt{deque}) is a dynamic array
|
||||
whose size can be changed at both ends of the array.
|
||||
Like a vector, a deque contains functions
|
||||
\texttt{push\_back} and \texttt{pop\_back}, but
|
||||
it also contains additional functions
|
||||
\texttt{push\_front} and \texttt{pop\_front}
|
||||
that are not available in a vector.
|
||||
|
||||
A deque can be used as follows:
|
||||
\begin{lstlisting}
|
||||
deque<int> d;
|
||||
d.push_back(5); // [5]
|
||||
|
@ -561,27 +559,26 @@ d.pop_back(); // [3,5]
|
|||
d.pop_front(); // [5]
|
||||
\end{lstlisting}
|
||||
|
||||
Pakan sisäinen toteutus on monimutkaisempi kuin
|
||||
vektorissa, minkä vuoksi se on
|
||||
vektoria raskaampi rakenne.
|
||||
Kuitenkin lisäyksen ja poiston
|
||||
aikavaativuus on keskimäärin $O(1)$ molemmissa päissä.
|
||||
The internal implementation of a deque
|
||||
is more complex than the implementation of a vector.
|
||||
For this reason, a deque is slower than a vector.
|
||||
Still, the time complexity of adding and removing
|
||||
elements is $O(1)$ on average at both ends.
|
||||
|
||||
\subsubsection{Pino}
|
||||
|
||||
\index{pino@pino}
|
||||
\index{stack}
|
||||
\index{stack@\texttt{stack}}
|
||||
|
||||
\key{Pino} (\texttt{stack}) on tietorakenne,
|
||||
joka tarjoaa kaksi $O(1)$-aikaista
|
||||
operaatiota:
|
||||
alkion lisäys pinon päälle ja alkion
|
||||
poisto pinon päältä.
|
||||
Pinossa ei ole mahdollista käsitellä muita
|
||||
alkioita kuin pinon päällimmäistä alkiota.
|
||||
|
||||
Seuraava koodi esittelee pinon käyttämistä:
|
||||
A \key{stack} (\texttt{stack})
|
||||
is a data structure that provides two
|
||||
$O(1)$ time operations:
|
||||
adding an element to the top,
|
||||
and removing an element from the top.
|
||||
It is only possible to access the top
|
||||
element of a stack.
|
||||
|
||||
The following code shows how a stack can be used:
|
||||
\begin{lstlisting}
|
||||
stack<int> s;
|
||||
s.push(3);
|
||||
|
@ -591,19 +588,19 @@ cout << s.top(); // 5
|
|||
s.pop();
|
||||
cout << s.top(); // 2
|
||||
\end{lstlisting}
|
||||
\subsubsection{Jono}
|
||||
\subsubsection{Queue}
|
||||
|
||||
\index{jono@jono}
|
||||
\index{queue}
|
||||
\index{queue@\texttt{queue}}
|
||||
|
||||
\key{Jono} (\texttt{queue}) on kuin pino,
|
||||
mutta alkion lisäys tapahtuu jonon loppuun
|
||||
ja alkion poisto tapahtuu jonon alusta.
|
||||
Jonossa on mahdollista käsitellä vain
|
||||
alussa ja lopussa olevaa alkiota.
|
||||
|
||||
Seuraava koodi esittelee jonon käyttämistä:
|
||||
A \key{queue} (\texttt{queue}) also
|
||||
provides two $O(1)$ time operations:
|
||||
adding a new element to the end,
|
||||
and removing the first element.
|
||||
It is only possible to access the first
|
||||
and the last element of a queue.
|
||||
|
||||
The following code shows how a queue can be used:
|
||||
\begin{lstlisting}
|
||||
queue<int> s;
|
||||
s.push(3);
|
||||
|
@ -613,44 +610,36 @@ cout << s.front(); // 3
|
|||
s.pop();
|
||||
cout << s.front(); // 2
|
||||
\end{lstlisting}
|
||||
%
|
||||
% Huomaa, että rakenteiden \texttt{stack} ja \texttt{queue}
|
||||
% sijasta voi aina käyttää rakenteita
|
||||
% \texttt{vector} ja \texttt{deque}, joilla voi
|
||||
% tehdä kaiken saman ja enemmän.
|
||||
% Kuitenkin \texttt{stack} ja \texttt{queue} ovat
|
||||
% kevyempiä ja hieman tehokkaampia rakenteita,
|
||||
% jos niiden operaatiot riittävät algoritmin toteuttamiseen.
|
||||
|
||||
\subsubsection{Prioriteettijono}
|
||||
\subsubsection{Priority queue}
|
||||
|
||||
\index{prioriteettijono@prioriteettijono}
|
||||
\index{keko@keko}
|
||||
\index{priority queue}
|
||||
\index{heap}
|
||||
\index{priority\_queue@\texttt{priority\_queue}}
|
||||
|
||||
\key{Prioriteettijono} (\texttt{priority\_queue})
|
||||
pitää yllä joukkoa alkioista.
|
||||
Sen operaatiot ovat alkion lisäys ja
|
||||
jonon tyypistä riippuen joko
|
||||
pienimmän alkion haku ja poisto tai
|
||||
suurimman alkion haku ja poisto.
|
||||
Lisäyksen ja poiston aikavaativuus on $O(\log n)$
|
||||
ja haun aikavaativuus on $O(1)$.
|
||||
A \key{priority queue} (\texttt{priority\_queue})
|
||||
maintains a set of elements.
|
||||
The supported operations are insertion and,
|
||||
depending on the type of the queue,
|
||||
retrieval and removal of
|
||||
either the minimum element or the maximum element.
|
||||
The time complexity is $O(\log n)$
|
||||
for insertion and removal and $O(1)$ for retrieval.
|
||||
|
||||
Vaikka prioriteettijonon operaatiot
|
||||
pystyy toteuttamaan myös \texttt{set}-ra\-ken\-teel\-la,
|
||||
prioriteettijonon etuna on,
|
||||
että sen kekoon perustuva sisäinen
|
||||
toteutus on yksinkertaisempi
|
||||
kuin \texttt{set}-rakenteen tasapainoinen binääripuu,
|
||||
minkä vuoksi rakenne on kevyempi ja
|
||||
operaatiot ovat tehokkaampia.
|
||||
While a set structure efficiently supports
|
||||
all the operations of a priority queue,
|
||||
the benefit in using a priority queue is
|
||||
that it has smaller constant factors.
|
||||
A priority queue is usually implemented using
|
||||
a heap structure that is much simpler than a
|
||||
balanced binary tree needed for an ordered set.
|
||||
|
||||
\begin{samepage}
|
||||
C++:n prioriteettijono toimii oletuksena niin,
|
||||
että alkiot ovat järjestyksessä suurimmasta pienimpään
|
||||
ja jonosta pystyy hakemaan ja poistamaan suurimman alkion.
|
||||
Seuraava koodi esittelee prioriteettijonon käyttämistä:
|
||||
As default, the elements in the C++
|
||||
priority queue are sorted in decreasing order,
|
||||
and it is possible to find and remove the
|
||||
largest element in the queue.
|
||||
The following code shows an example:
|
||||
|
||||
\begin{lstlisting}
|
||||
priority_queue<int> q;
|
||||
|
@ -668,76 +657,77 @@ q.pop();
|
|||
\end{lstlisting}
|
||||
\end{samepage}
|
||||
|
||||
Seuraava määrittely luo käänteisen prioriteettijonon,
|
||||
jossa jonosta pystyy hakemaan ja poistamaan pienimmän alkion:
|
||||
The following definition creates a priority queue
|
||||
that supports finding and removing the minimum element:
|
||||
|
||||
\begin{lstlisting}
|
||||
priority_queue<int,vector<int>,greater<int>> q;
|
||||
\end{lstlisting}
|
||||
|
||||
\section{Vertailu järjestämiseen}
|
||||
\section{Comparison to sorting}
|
||||
|
||||
Monen tehtävän voi ratkaista tehokkaasti joko
|
||||
käyttäen sopivia tietorakenteita
|
||||
tai taulukon järjestämistä.
|
||||
Vaikka erilaiset ratkaisutavat olisivat kaikki
|
||||
periaatteessa tehokkaita, niissä voi olla
|
||||
käytännössä merkittäviä eroja.
|
||||
Often it's possible to solve a problem
|
||||
using either data structures or sorting.
|
||||
Sometimes there are remarkable differences
|
||||
in the actual efficiency of these approaches,
|
||||
which may be hidden in their time complexities.
|
||||
|
||||
Tarkastellaan ongelmaa, jossa
|
||||
annettuna on kaksi listaa $A$ ja $B$,
|
||||
joista kummassakin on $n$ kokonaislukua.
|
||||
Tehtävänä on selvittää, moniko luku
|
||||
esiintyy kummassakin listassa.
|
||||
Esimerkiksi jos listat ovat
|
||||
\[A = [5,2,8,9,4] \hspace{10px} \textrm{ja} \hspace{10px} B = [3,2,9,5],\]
|
||||
niin vastaus on 3, koska luvut 2, 5
|
||||
ja 9 esiintyvät kummassakin listassa.
|
||||
Suoraviivainen ratkaisu tehtävään on käydä läpi
|
||||
kaikki lukuparit ajassa $O(n^2)$, mutta seuraavaksi
|
||||
keskitymme tehokkaampiin ratkaisuihin.
|
||||
Let us consider a problem where
|
||||
we are given two lists $A$ and $B$
|
||||
that both contain $n$ integers.
|
||||
Our task is to calculate the number of integers
|
||||
that belong to both of the lists.
|
||||
For example, for the lists
|
||||
\[A = [5,2,8,9,4] \hspace{10px} \textrm{and} \hspace{10px} B = [3,2,9,5],\]
|
||||
the answer is 3 because the numbers 2, 5
|
||||
and 9 belong to both of the lists.
|
||||
|
||||
\subsubsection{Ratkaisu 1}
|
||||
A straightforward solution for the problem is
|
||||
to go through all pairs of numbers in $O(n^2)$ time,
|
||||
but next we will concentrate on
|
||||
more efficient solutions.
|
||||
|
||||
Tallennetaan listan $A$ luvut joukkoon
|
||||
ja käydään sitten läpi listan $B$ luvut ja
|
||||
tarkistetaan jokaisesta, esiintyykö se myös listassa $A$.
|
||||
Joukon ansiosta on tehokasta tarkastaa,
|
||||
esiintyykö listan $B$ luku listassa $A$.
|
||||
Kun joukko toteutetaan \texttt{set}-rakenteella,
|
||||
algoritmin aikavaativuus on $O(n \log n)$.
|
||||
\subsubsection{Solution 1}
|
||||
|
||||
\subsubsection{Ratkaisu 2}
|
||||
We construct a set of the numbers in $A$,
|
||||
and after this, iterate through the numbers
|
||||
in $B$ and check for each number if it
|
||||
also belongs to $A$.
|
||||
This is efficient because the numbers in $A$
|
||||
are in a set.
|
||||
Using the \texttt{set} structure,
|
||||
the time complexity of the algorithm is $O(n \log n)$.
|
||||
|
||||
Joukon ei tarvitse säilyttää lukuja
|
||||
järjestyksessä, joten
|
||||
\texttt{set}-ra\-ken\-teen sijasta voi
|
||||
käyttää myös \texttt{unordered\_set}-ra\-ken\-net\-ta.
|
||||
Tämä on helppo tapa parantaa algoritmin
|
||||
tehokkuutta, koska
|
||||
algoritmin toteutus säilyy samana ja vain tietorakenne vaihtuu.
|
||||
Uuden algoritmin aikavaativuus on $O(n)$.
|
||||
\subsubsection{Solution 2}
|
||||
|
||||
\subsubsection{Ratkaisu 3}
|
||||
It is not needed to maintain an ordered set,
|
||||
so instead of the \texttt{set} structure
|
||||
we can also use the \texttt{unordered\_set} structure.
|
||||
This is an easy way to make the algorithm
|
||||
more efficient because we only have to change
|
||||
the data structure that the algorithm uses.
|
||||
The time complexity of the new algorithm is $O(n)$.
|
||||
|
||||
Tietorakenteiden sijasta voimme käyttää järjestämistä.
|
||||
Järjestetään ensin listat $A$ ja $B$,
|
||||
minkä jälkeen yhteiset luvut voi löytää
|
||||
käymällä listat rinnakkain läpi.
|
||||
Järjestämisen aikavaativuus on $O(n \log n)$ ja
|
||||
läpikäynnin aikavaativuus on $O(n)$,
|
||||
joten kokonaisaikavaativuus on $O(n \log n)$.
|
||||
\subsubsection{Solution 3}
|
||||
|
||||
\subsubsection{Tehokkuusvertailu}
|
||||
Instead of data structures, we can use sorting.
|
||||
First, we sort both lists $A$ and $B$.
|
||||
After this, we iterate through both the lists
|
||||
at the same time and find the common elements.
|
||||
The time complexity of sorting is $O(n \log n)$,
|
||||
and the rest of the algorithm works in $O(n)$ time,
|
||||
so the total time complexity is $O(n \log n)$.
|
||||
|
||||
Seuraavassa taulukossa on mittaustuloksia
|
||||
äskeisten algoritmien tehokkuudesta,
|
||||
kun $n$ vaihtelee ja listojen luvut ovat
|
||||
satunnaisia lukuja välillä $1 \ldots 10^9$:
|
||||
\subsubsection{Efficiency comparison}
|
||||
|
||||
The following table shows how efficient
|
||||
the above algorithms are when $n$ varies and
|
||||
the elements in the lists are random
|
||||
integers between $1 \ldots 10^9$:
|
||||
|
||||
\begin{center}
|
||||
\begin{tabular}{rrrr}
|
||||
$n$ & ratkaisu 1 & ratkaisu 2 & ratkaisu 3 \\
|
||||
$n$ & solution 1 & solution 2 & solution 3 \\
|
||||
\hline
|
||||
$10^6$ & $1{,}5$ s & $0{,}3$ s & $0{,}2$ s \\
|
||||
$2 \cdot 10^6$ & $3{,}7$ s & $0{,}8$ s & $0{,}3$ s \\
|
||||
|
@ -747,26 +737,23 @@ $5 \cdot 10^6$ & $10{,}0$ s & $2{,}3$ s & $0{,}9$ s \\
|
|||
\end{tabular}
|
||||
\end{center}
|
||||
|
||||
Ratkaisut 1 ja 2 ovat muuten samanlaisia,
|
||||
mutta ratkaisu 1 käyttää \texttt{set}-rakennetta,
|
||||
kun taas ratkaisu 2 käyttää
|
||||
\texttt{unordered\_set}-rakennetta.
|
||||
Tässä tapauksessa tällä valinnalla on
|
||||
merkittävä vaikutus suoritusaikaan,
|
||||
koska ratkaisu 2 on 4–5 kertaa
|
||||
nopeampi kuin ratkaisu 1.
|
||||
Solutions 1 and 2 are equal except that
|
||||
solution 1 uses the \texttt{set} structure
|
||||
and solution 2 uses the \texttt{unordered\_set} structure.
|
||||
In this case, this choice has a big effect on
|
||||
the running time becase solution 2
|
||||
is 4–5 times faster than solution 1.
|
||||
|
||||
Tehokkain ratkaisu on kuitenkin järjestämistä
|
||||
käyttävä ratkaisu 3, joka on vielä puolet
|
||||
nopeampi kuin ratkaisu 2.
|
||||
Kiinnostavaa on, että sekä ratkaisun 1 että
|
||||
ratkaisun 3 aikavaativuus on $O(n \log n)$,
|
||||
mutta siitä huolimatta
|
||||
ratkaisu 3 vie aikaa vain kymmenesosan.
|
||||
Tämän voi selittää sillä, että
|
||||
järjestäminen on kevyt
|
||||
operaatio ja se täytyy tehdä vain kerran
|
||||
ratkaisussa 3 algoritmin alussa,
|
||||
minkä jälkeen algoritmin loppuosa on lineaarinen.
|
||||
Ratkaisu 1 taas pitää yllä monimutkaista
|
||||
tasapainoista binääripuuta koko algoritmin ajan.
|
||||
However, the most efficient solution is solution 3
|
||||
that uses sorting.
|
||||
It only uses half of the time compared to solution 2.
|
||||
Interestingly, the time complexity of both
|
||||
solution 1 and solution 3 is $O(n \log n)$,
|
||||
but despite this, solution 3 is ten times faster.
|
||||
The explanation for this is that
|
||||
sorting is a simple procedure and it is done
|
||||
only once at the beginning of solution 3,
|
||||
and the rest of the algorithm works in linear time.
|
||||
On the other hand,
|
||||
solution 3 maintains a complex balanced binary tree
|
||||
during the whole algorithm.
|
Loading…
Reference in New Issue