Corrections
This commit is contained in:
parent
faa9ca2518
commit
3dd874a4fa
131
luku01.tex
131
luku01.tex
|
@ -6,7 +6,7 @@ Competitive programming combines two topics:
|
||||||
The \key{design of algorithms} consists of problem solving
|
The \key{design of algorithms} consists of problem solving
|
||||||
and mathematical thinking.
|
and mathematical thinking.
|
||||||
Skills for analyzing problems and solving them
|
Skills for analyzing problems and solving them
|
||||||
creatively is needed.
|
creatively are needed.
|
||||||
An algorithm for solving a problem
|
An algorithm for solving a problem
|
||||||
has to be both correct and efficient,
|
has to be both correct and efficient,
|
||||||
and the core of the problem is often
|
and the core of the problem is often
|
||||||
|
@ -28,14 +28,14 @@ are graded by testing an implemented algorithm
|
||||||
using a set of test cases.
|
using a set of test cases.
|
||||||
Thus, it is not enough that the idea of the
|
Thus, it is not enough that the idea of the
|
||||||
algorithm is correct, but the implementation has
|
algorithm is correct, but the implementation has
|
||||||
to be correct as well.
|
also to be correct.
|
||||||
|
|
||||||
Good coding style in contests is
|
Good coding style in contests is
|
||||||
straightforward and concise.
|
straightforward and concise.
|
||||||
The solutions should be written quickly,
|
The programs should be written quickly,
|
||||||
because there is not much time available.
|
because there is not much time available.
|
||||||
Unlike in traditional software engineering,
|
Unlike in traditional software engineering,
|
||||||
the solutions are short (usually at most some
|
the programs are short (usually at most some
|
||||||
hundreds of lines) and it is not needed to
|
hundreds of lines) and it is not needed to
|
||||||
maintain them after the contest.
|
maintain them after the contest.
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ For example, in Google Code Jam 2016,
|
||||||
among the best 3,000 participants,
|
among the best 3,000 participants,
|
||||||
73 \% used C++,
|
73 \% used C++,
|
||||||
15 \% used Python and
|
15 \% used Python and
|
||||||
10 \% used Java\footnote{\url{https://www.go-hero.net/jam/16}}.
|
10 \% used Java. %\footnote{\url{https://www.go-hero.net/jam/16}}
|
||||||
Some participants also used several languages.
|
Some participants also used several languages.
|
||||||
|
|
||||||
Many people think that C++ is the best choice
|
Many people think that C++ is the best choice
|
||||||
|
@ -65,11 +65,11 @@ of data structures and algorithms.
|
||||||
On the other hand, it is good to
|
On the other hand, it is good to
|
||||||
master several languages and know
|
master several languages and know
|
||||||
their strengths.
|
their strengths.
|
||||||
For example, if large numbers are needed
|
For example, if large integers are needed
|
||||||
in the problem,
|
in the problem,
|
||||||
Python can be a good choice because it
|
Python can be a good choice, because it
|
||||||
contains a built-in library for handling
|
contains built-in operations for
|
||||||
large numbers.
|
calculating with large integers.
|
||||||
Still, most problems in programming contests
|
Still, most problems in programming contests
|
||||||
are set so that
|
are set so that
|
||||||
using a specific programming language
|
using a specific programming language
|
||||||
|
@ -99,8 +99,8 @@ int main() {
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
The \texttt{\#include} line at the beginning
|
The \texttt{\#include} line at the beginning
|
||||||
of the code is a feature in the \texttt{g++} compiler
|
of the code is a feature of the \texttt{g++} compiler
|
||||||
that allows us to include the whole standard library.
|
that allows us to include the entire standard library.
|
||||||
Thus, it is not needed to separately include
|
Thus, it is not needed to separately include
|
||||||
libraries such as \texttt{iostream},
|
libraries such as \texttt{iostream},
|
||||||
\texttt{vector} and \texttt{algorithm},
|
\texttt{vector} and \texttt{algorithm},
|
||||||
|
@ -112,7 +112,7 @@ of the standard library can be used directly
|
||||||
in the code.
|
in the code.
|
||||||
Without the \texttt{using} line we should write,
|
Without the \texttt{using} line we should write,
|
||||||
for example, \texttt{std::cout},
|
for example, \texttt{std::cout},
|
||||||
but now it is enough to write \texttt{cout}.
|
but now it suffices to write \texttt{cout}.
|
||||||
|
|
||||||
The code can be compiled using the following command:
|
The code can be compiled using the following command:
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ g++ -std=c++11 -O2 -Wall code.cpp -o code
|
||||||
|
|
||||||
This command produces a binary file \texttt{code}
|
This command produces a binary file \texttt{code}
|
||||||
from the source code \texttt{code.cpp}.
|
from the source code \texttt{code.cpp}.
|
||||||
The compiler obeys the C++11 standard
|
The compiler follows the C++11 standard
|
||||||
(\texttt{-std=c++11}),
|
(\texttt{-std=c++11}),
|
||||||
optimizes the code (\texttt{-O2})
|
optimizes the code (\texttt{-O2})
|
||||||
and shows warnings about possible errors (\texttt{-Wall}).
|
and shows warnings about possible errors (\texttt{-Wall}).
|
||||||
|
@ -152,8 +152,8 @@ cin >> a >> b >> x;
|
||||||
|
|
||||||
This kind of code always works,
|
This kind of code always works,
|
||||||
assuming that there is at least one space
|
assuming that there is at least one space
|
||||||
or one newline between each element in the input.
|
or newline between each element in the input.
|
||||||
For example, the above code accepts
|
For example, the above code can read
|
||||||
both the following inputs:
|
both the following inputs:
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
123 456 monkey
|
123 456 monkey
|
||||||
|
@ -203,7 +203,7 @@ printf("%d %d\n", a, b);
|
||||||
|
|
||||||
Sometimes the program should read a whole line
|
Sometimes the program should read a whole line
|
||||||
from the input, possibly with spaces.
|
from the input, possibly with spaces.
|
||||||
This can be accomplished using the
|
This can be accomplished by using the
|
||||||
\texttt{getline} function:
|
\texttt{getline} function:
|
||||||
|
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
|
@ -212,7 +212,7 @@ getline(cin, s);
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
If the amount of data is unknown, the following
|
If the amount of data is unknown, the following
|
||||||
loop can be handy:
|
loop is useful:
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
while (cin >> x) {
|
while (cin >> x) {
|
||||||
// code
|
// code
|
||||||
|
@ -231,11 +231,11 @@ but add the following lines to the beginning of the code:
|
||||||
freopen("input.txt", "r", stdin);
|
freopen("input.txt", "r", stdin);
|
||||||
freopen("output.txt", "w", stdout);
|
freopen("output.txt", "w", stdout);
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
After this, the code reads the input from the file
|
After this, the program reads the input from the file
|
||||||
''input.txt'' and writes the output to the file
|
''input.txt'' and writes the output to the file
|
||||||
''output.txt''.
|
''output.txt''.
|
||||||
|
|
||||||
\section{Handling numbers}
|
\section{Working with numbers}
|
||||||
|
|
||||||
\index{integer}
|
\index{integer}
|
||||||
|
|
||||||
|
@ -299,19 +299,19 @@ when $x$ is divided by $m$.
|
||||||
For example, $17 \bmod 5 = 2$,
|
For example, $17 \bmod 5 = 2$,
|
||||||
because $17 = 3 \cdot 5 + 2$.
|
because $17 = 3 \cdot 5 + 2$.
|
||||||
|
|
||||||
Sometimes, the answer for a problem is a
|
Sometimes, the answer to a problem is a
|
||||||
very large number but it is enough to
|
very large number but it is enough to
|
||||||
output it ''modulo $m$'', i.e.,
|
output it ''modulo $m$'', i.e.,
|
||||||
the remainder when the answer is divided by $m$
|
the remainder when the answer is divided by $m$
|
||||||
(for example, ''modulo $10^9+7$'').
|
(for example, ''modulo $10^9+7$'').
|
||||||
The idea is that even if the actual answer
|
The idea is that even if the actual answer
|
||||||
may be very large,
|
may be very large,
|
||||||
it is enough to use the types
|
it suffices to use the types
|
||||||
\texttt{int} and \texttt{long long}.
|
\texttt{int} and \texttt{long long}.
|
||||||
|
|
||||||
An important property of the remainder is that
|
An important property of the remainder is that
|
||||||
in addition, subtraction and multiplication,
|
in addition, subtraction and multiplication,
|
||||||
the remainder can be calculated before the operation:
|
the remainder can be taken before the operation:
|
||||||
|
|
||||||
\[
|
\[
|
||||||
\begin{array}{rcr}
|
\begin{array}{rcr}
|
||||||
|
@ -321,7 +321,7 @@ the remainder can be calculated before the operation:
|
||||||
\end{array}
|
\end{array}
|
||||||
\]
|
\]
|
||||||
|
|
||||||
Thus, we can calculate the remainder after every operation
|
Thus, we can take the remainder after every operation
|
||||||
and the numbers will never become too large.
|
and the numbers will never become too large.
|
||||||
|
|
||||||
For example, the following code calculates $n!$,
|
For example, the following code calculates $n!$,
|
||||||
|
@ -339,8 +339,8 @@ be between $0\ldots m-1$.
|
||||||
However, in C++ and other languages,
|
However, in C++ and other languages,
|
||||||
the remainder of a negative number
|
the remainder of a negative number
|
||||||
is either zero or negative.
|
is either zero or negative.
|
||||||
An easy way to make sure this will
|
An easy way to make sure there
|
||||||
not happen is to first calculate
|
are no negative remainders is to first calculate
|
||||||
the remainder as usual and then add $m$
|
the remainder as usual and then add $m$
|
||||||
if the result is negative:
|
if the result is negative:
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
|
@ -378,7 +378,8 @@ printf("%.9f\n", x);
|
||||||
|
|
||||||
A difficulty when using floating point numbers
|
A difficulty when using floating point numbers
|
||||||
is that some numbers cannot be represented
|
is that some numbers cannot be represented
|
||||||
accurately but there will be rounding errors.
|
accurately as floating point numbers,
|
||||||
|
but there will be rounding errors.
|
||||||
For example, the result of the following code
|
For example, the result of the following code
|
||||||
is surprising:
|
is surprising:
|
||||||
|
|
||||||
|
@ -387,14 +388,14 @@ double x = 0.3*3+0.1;
|
||||||
printf("%.20f\n", x); // 0.99999999999999988898
|
printf("%.20f\n", x); // 0.99999999999999988898
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
Because of a rounding error,
|
Due to a rounding error,
|
||||||
the value of \texttt{x} is a bit less than 1,
|
the value of \texttt{x} is a bit smaller than 1,
|
||||||
while the correct value would be 1.
|
while the correct value would be 1.
|
||||||
|
|
||||||
It is risky to compare floating point numbers
|
It is risky to compare floating point numbers
|
||||||
with the \texttt{==} operator,
|
with the \texttt{==} operator,
|
||||||
because it is possible that the values should
|
because it is possible that the values should
|
||||||
be equal but they are not due to rounding errors.
|
be equal but they are not because of rounding.
|
||||||
A better way to compare floating point numbers
|
A better way to compare floating point numbers
|
||||||
is to assume that two numbers are equal
|
is to assume that two numbers are equal
|
||||||
if the difference between them is $\varepsilon$,
|
if the difference between them is $\varepsilon$,
|
||||||
|
@ -414,12 +415,12 @@ integers up to a certain limit can be still
|
||||||
represented accurately.
|
represented accurately.
|
||||||
For example, using \texttt{double},
|
For example, using \texttt{double},
|
||||||
it is possible to accurately represent all
|
it is possible to accurately represent all
|
||||||
integers having absolute value at most $2^{53}$.
|
integers whose absolute value is at most $2^{53}$.
|
||||||
|
|
||||||
\section{Shortening code}
|
\section{Shortening code}
|
||||||
|
|
||||||
Short code is ideal in competitive programming,
|
Short code is ideal in competitive programming,
|
||||||
because algorithms should be implemented
|
because programs should be written
|
||||||
as fast as possible.
|
as fast as possible.
|
||||||
Because of this, competitive programmers often define
|
Because of this, competitive programmers often define
|
||||||
shorter names for datatypes and other parts of code.
|
shorter names for datatypes and other parts of code.
|
||||||
|
@ -450,7 +451,7 @@ cout << a*b << "\n";
|
||||||
The command \texttt{typedef}
|
The command \texttt{typedef}
|
||||||
can also be used with more complex types.
|
can also be used with more complex types.
|
||||||
For example, the following code gives
|
For example, the following code gives
|
||||||
the name \texttt{vi} for a vector of integers,
|
the name \texttt{vi} for a vector of integers
|
||||||
and the name \texttt{pi} for a pair
|
and the name \texttt{pi} for a pair
|
||||||
that contains two integers.
|
that contains two integers.
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
|
@ -511,16 +512,16 @@ REP(i,1,n) {
|
||||||
|
|
||||||
Mathematics plays an important role in competitive
|
Mathematics plays an important role in competitive
|
||||||
programming, and it is not possible to become
|
programming, and it is not possible to become
|
||||||
a successful competitive programmer without good skills
|
a successful competitive programmer without
|
||||||
in mathematics.
|
having good mathematical skills.
|
||||||
This section covers some important
|
This section discusses some important
|
||||||
mathematical concepts and formulas that
|
mathematical concepts and formulas that
|
||||||
are needed later in the book.
|
are needed later in the book.
|
||||||
|
|
||||||
\subsubsection{Sum formulas}
|
\subsubsection{Sum formulas}
|
||||||
|
|
||||||
Each sum of the form
|
Each sum of the form
|
||||||
\[\sum_{x=1}^n x^k = 1^k+2^k+3^k+\ldots+n^k\]
|
\[\sum_{x=1}^n x^k = 1^k+2^k+3^k+\ldots+n^k,\]
|
||||||
where $k$ is a positive integer,
|
where $k$ is a positive integer,
|
||||||
has a closed-form formula that is a
|
has a closed-form formula that is a
|
||||||
polynomial of degree $k+1$.
|
polynomial of degree $k+1$.
|
||||||
|
@ -529,13 +530,14 @@ For example,
|
||||||
and
|
and
|
||||||
\[\sum_{x=1}^n x^2 = 1^2+2^2+3^2+\ldots+n^2 = \frac{n(n+1)(2n+1)}{6}.\]
|
\[\sum_{x=1}^n x^2 = 1^2+2^2+3^2+\ldots+n^2 = \frac{n(n+1)(2n+1)}{6}.\]
|
||||||
|
|
||||||
An \key{arithmetic sum} is a sum \index{arithmetic sum}
|
An \key{arithmetic progression} is a \index{arithmetic progression}
|
||||||
|
sequence of numbers
|
||||||
where the difference between any two consecutive
|
where the difference between any two consecutive
|
||||||
numbers is constant.
|
numbers is constant.
|
||||||
For example,
|
For example,
|
||||||
\[3+7+11+15\]
|
\[3, 7, 11, 15\]
|
||||||
is an arithmetic sum with constant 4.
|
is an arithmetic progression with constant 4.
|
||||||
An arithmetic sum can be calculated
|
The sum of an arithmetic progression can be calculated
|
||||||
using the formula
|
using the formula
|
||||||
\[\frac{n(a+b)}{2}\]
|
\[\frac{n(a+b)}{2}\]
|
||||||
where $a$ is the first number,
|
where $a$ is the first number,
|
||||||
|
@ -547,14 +549,15 @@ The formula is based on the fact
|
||||||
that the sum consists of $n$ numbers and
|
that the sum consists of $n$ numbers and
|
||||||
the value of each number is $(a+b)/2$ on average.
|
the value of each number is $(a+b)/2$ on average.
|
||||||
|
|
||||||
\index{geometric sum}
|
\index{geometric progression}
|
||||||
A \key{geometric sum} is a sum
|
A \key{geometric progression} is a sequence
|
||||||
|
of numbers
|
||||||
where the ratio between any two consecutive
|
where the ratio between any two consecutive
|
||||||
numbers is constant.
|
numbers is constant.
|
||||||
For example,
|
For example,
|
||||||
\[3+6+12+24\]
|
\[3,6,12,24\]
|
||||||
is a geometric sum with constant 2.
|
is a geometric progression with constant 2.
|
||||||
A geometric sum can be calculated
|
The sum of a geometric progression can be calculated
|
||||||
using the formula
|
using the formula
|
||||||
\[\frac{bx-a}{x-1}\]
|
\[\frac{bx-a}{x-1}\]
|
||||||
where $a$ is the first number,
|
where $a$ is the first number,
|
||||||
|
@ -571,7 +574,7 @@ and solving the equation
|
||||||
\[ xS-S = bx-a\]
|
\[ xS-S = bx-a\]
|
||||||
yields the formula.
|
yields the formula.
|
||||||
|
|
||||||
A special case of a geometric sum is the formula
|
A special case of a sum of a geometric progression is the formula
|
||||||
\[1+2+4+8+\ldots+2^{n-1}=2^n-1.\]
|
\[1+2+4+8+\ldots+2^{n-1}=2^n-1.\]
|
||||||
|
|
||||||
\index{harmonic sum}
|
\index{harmonic sum}
|
||||||
|
@ -579,7 +582,7 @@ A special case of a geometric sum is the formula
|
||||||
A \key{harmonic sum} is a sum of the form
|
A \key{harmonic sum} is a sum of the form
|
||||||
\[ \sum_{x=1}^n \frac{1}{x} = 1+\frac{1}{2}+\frac{1}{3}+\ldots+\frac{1}{n}.\]
|
\[ \sum_{x=1}^n \frac{1}{x} = 1+\frac{1}{2}+\frac{1}{3}+\ldots+\frac{1}{n}.\]
|
||||||
|
|
||||||
An upper bound for the harmonic sum is $\log_2(n)+1$.
|
An upper bound for a harmonic sum is $\log_2(n)+1$.
|
||||||
Namely, we can
|
Namely, we can
|
||||||
modify each term $1/k$ so that $k$ becomes
|
modify each term $1/k$ so that $k$ becomes
|
||||||
the nearest power of two that does not exceed $k$.
|
the nearest power of two that does not exceed $k$.
|
||||||
|
@ -589,7 +592,7 @@ the sum as follows:
|
||||||
1+\frac{1}{2}+\frac{1}{2}+\frac{1}{4}+\frac{1}{4}+\frac{1}{4}.\]
|
1+\frac{1}{2}+\frac{1}{2}+\frac{1}{4}+\frac{1}{4}+\frac{1}{4}.\]
|
||||||
This upper bound consists of $\log_2(n)+1$ parts
|
This upper bound consists of $\log_2(n)+1$ parts
|
||||||
($1$, $2 \cdot 1/2$, $4 \cdot 1/4$, etc.),
|
($1$, $2 \cdot 1/2$, $4 \cdot 1/4$, etc.),
|
||||||
and the sum of each part is at most 1.
|
and the value of each part is at most 1.
|
||||||
|
|
||||||
\subsubsection{Set theory}
|
\subsubsection{Set theory}
|
||||||
|
|
||||||
|
@ -607,7 +610,7 @@ For example, the set
|
||||||
\[X=\{2,4,7\}\]
|
\[X=\{2,4,7\}\]
|
||||||
contains elements 2, 4 and 7.
|
contains elements 2, 4 and 7.
|
||||||
The symbol $\emptyset$ denotes an empty set,
|
The symbol $\emptyset$ denotes an empty set,
|
||||||
and $|S|$ denotes the size of the set $S$,
|
and $|S|$ denotes the size of a set $S$,
|
||||||
i.e., the number of elements in the set.
|
i.e., the number of elements in the set.
|
||||||
For example, in the above set, $|X|=3$.
|
For example, in the above set, $|X|=3$.
|
||||||
|
|
||||||
|
@ -654,15 +657,11 @@ $\{2\}$, $\{4\}$, $\{7\}$, $\{2,4\}$, $\{2,7\}$, $\{4,7\}$ and $\{2,4,7\}$.
|
||||||
\end{center}
|
\end{center}
|
||||||
|
|
||||||
Often used sets are
|
Often used sets are
|
||||||
|
$\mathbb{N}$ (natural numbers),
|
||||||
\begin{itemize}[noitemsep]
|
$\mathbb{Z}$ (integers),
|
||||||
\item $\mathbb{N}$ (natural numbers),
|
$\mathbb{Q}$ (rational numbers) and
|
||||||
\item $\mathbb{Z}$ (integers),
|
$\mathbb{R}$ (real numbers).
|
||||||
\item $\mathbb{Q}$ (rational numbers) and
|
The set $\mathbb{N}$
|
||||||
\item $\mathbb{R}$ (real numbers).
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
The set $\mathbb{N}$ of natural numbers
|
|
||||||
can be defined in two ways, depending
|
can be defined in two ways, depending
|
||||||
on the situation:
|
on the situation:
|
||||||
either $\mathbb{N}=\{0,1,2,\ldots\}$
|
either $\mathbb{N}=\{0,1,2,\ldots\}$
|
||||||
|
@ -707,7 +706,7 @@ $A$ & $B$ & $\lnot A$ & $\lnot B$ & $A \land B$ & $A \lor B$ & $A \Rightarrow B$
|
||||||
\end{tabular}
|
\end{tabular}
|
||||||
\end{center}
|
\end{center}
|
||||||
|
|
||||||
The expression $\lnot A$ has the reverse value of $A$.
|
The expression $\lnot A$ has the opposite value of $A$.
|
||||||
The expression $A \land B$ is true if both $A$ and $B$
|
The expression $A \land B$ is true if both $A$ and $B$
|
||||||
are true,
|
are true,
|
||||||
and the expression $A \lor B$ is true if $A$ or $B$ or both
|
and the expression $A \lor B$ is true if $A$ or $B$ or both
|
||||||
|
@ -729,7 +728,7 @@ Using this definition, $P(7)$ is true but $P(8)$ is false.
|
||||||
\index{quantifier}
|
\index{quantifier}
|
||||||
|
|
||||||
A \key{quantifier} connects a logical expression
|
A \key{quantifier} connects a logical expression
|
||||||
to elements in a set.
|
to the elements of a set.
|
||||||
The most important quantifiers are
|
The most important quantifiers are
|
||||||
$\forall$ (\key{for all}) and $\exists$ (\key{there is}).
|
$\forall$ (\key{for all}) and $\exists$ (\key{there is}).
|
||||||
For example,
|
For example,
|
||||||
|
@ -746,7 +745,7 @@ For example,
|
||||||
\[\forall x ((x>1 \land \lnot P(x)) \Rightarrow (\exists a (\exists b (x = ab \land a > 1 \land b > 1))))\]
|
\[\forall x ((x>1 \land \lnot P(x)) \Rightarrow (\exists a (\exists b (x = ab \land a > 1 \land b > 1))))\]
|
||||||
means that if a number $x$ is larger than 1
|
means that if a number $x$ is larger than 1
|
||||||
and not a prime number,
|
and not a prime number,
|
||||||
then there exist numbers $a$ and $b$
|
then there are numbers $a$ and $b$
|
||||||
that are larger than $1$ and whose product is $x$.
|
that are larger than $1$ and whose product is $x$.
|
||||||
This proposition is true in the set of integers.
|
This proposition is true in the set of integers.
|
||||||
|
|
||||||
|
@ -804,7 +803,7 @@ of the logarithm.
|
||||||
According to the definition,
|
According to the definition,
|
||||||
$\log_k(x)=a$ exactly when $k^a=x$.
|
$\log_k(x)=a$ exactly when $k^a=x$.
|
||||||
|
|
||||||
A useful interpretation in algorithm design is
|
A useful property of logarithms is
|
||||||
that $\log_k(x)$ equals the number of times
|
that $\log_k(x)$ equals the number of times
|
||||||
we have to divide $x$ by $k$ before we reach
|
we have to divide $x$ by $k$ before we reach
|
||||||
the number 1.
|
the number 1.
|
||||||
|
@ -813,9 +812,9 @@ because 5 divisions are needed:
|
||||||
|
|
||||||
\[32 \rightarrow 16 \rightarrow 8 \rightarrow 4 \rightarrow 2 \rightarrow 1 \]
|
\[32 \rightarrow 16 \rightarrow 8 \rightarrow 4 \rightarrow 2 \rightarrow 1 \]
|
||||||
|
|
||||||
Logarithms are often needed in the analysis of
|
Logarithms are often used in the analysis of
|
||||||
algorithms, because many efficient algorithms
|
algorithms, because many efficient algorithms
|
||||||
divide in half something at each step.
|
halve something at each step.
|
||||||
Hence, we can estimate the efficiency of such algorithms
|
Hence, we can estimate the efficiency of such algorithms
|
||||||
using logarithms.
|
using logarithms.
|
||||||
|
|
||||||
|
@ -837,9 +836,9 @@ The \key{natural logarithm} $\ln(x)$ of a number $x$
|
||||||
is a logarithm whose base is $e \approx 2{,}71828$.
|
is a logarithm whose base is $e \approx 2{,}71828$.
|
||||||
|
|
||||||
Another property of logarithms is that
|
Another property of logarithms is that
|
||||||
the number of digits of a number $x$ in base $b$ is
|
the number of digits of an integer $x$ in base $b$ is
|
||||||
$\lfloor \log_b(x)+1 \rfloor$.
|
$\lfloor \log_b(x)+1 \rfloor$.
|
||||||
For example, the representation of
|
For example, the representation of
|
||||||
the number $123$ in base $2$ is 1111011 and
|
$123$ in base $2$ is 1111011 and
|
||||||
$\lfloor \log_2(123)+1 \rfloor = 7$.
|
$\lfloor \log_2(123)+1 \rfloor = 7$.
|
||||||
|
|
||||||
|
|
50
luku02.tex
50
luku02.tex
|
@ -59,7 +59,7 @@ for (int i = 1; i <= n; i++) {
|
||||||
|
|
||||||
\subsubsection*{Order of magnitude}
|
\subsubsection*{Order of magnitude}
|
||||||
|
|
||||||
A time complexity does not indicate the exact number
|
A time complexity does not tell us the exact number
|
||||||
of times the code inside a loop is executed,
|
of times the code inside a loop is executed,
|
||||||
but it only shows the order of magnitude.
|
but it only shows the order of magnitude.
|
||||||
In the following examples, the code inside the loop
|
In the following examples, the code inside the loop
|
||||||
|
@ -210,7 +210,7 @@ $n$ must be divided by 2 to get 1.
|
||||||
\item[$O(\sqrt n)$]
|
\item[$O(\sqrt n)$]
|
||||||
A \key{square root algorithm} is slower than
|
A \key{square root algorithm} is slower than
|
||||||
$O(\log n)$ but faster than $O(n)$.
|
$O(\log n)$ but faster than $O(n)$.
|
||||||
A special feature of square roots is that
|
A special property of square roots is that
|
||||||
$\sqrt n = n/\sqrt n$, so the square root $\sqrt n$ lies
|
$\sqrt n = n/\sqrt n$, so the square root $\sqrt n$ lies
|
||||||
in some sense in the middle of the input.
|
in some sense in the middle of the input.
|
||||||
|
|
||||||
|
@ -225,7 +225,7 @@ reporting the answer.
|
||||||
|
|
||||||
\item[$O(n \log n)$]
|
\item[$O(n \log n)$]
|
||||||
This time complexity often indicates that the
|
This time complexity often indicates that the
|
||||||
algorithm sorts the input
|
algorithm sorts the input,
|
||||||
because the time complexity of efficient
|
because the time complexity of efficient
|
||||||
sorting algorithms is $O(n \log n)$.
|
sorting algorithms is $O(n \log n)$.
|
||||||
Another possibility is that the algorithm
|
Another possibility is that the algorithm
|
||||||
|
@ -237,14 +237,14 @@ takes $O(\log n)$ time.
|
||||||
A \key{quadratic} algorithm often contains
|
A \key{quadratic} algorithm often contains
|
||||||
two nested loops.
|
two nested loops.
|
||||||
It is possible to go through all pairs of
|
It is possible to go through all pairs of
|
||||||
input elements in $O(n^2)$ time.
|
the input elements in $O(n^2)$ time.
|
||||||
|
|
||||||
\item[$O(n^3)$]
|
\item[$O(n^3)$]
|
||||||
\index{cubic algorithm}
|
\index{cubic algorithm}
|
||||||
A \key{cubic} algorithm often contains
|
A \key{cubic} algorithm often contains
|
||||||
three nested loops.
|
three nested loops.
|
||||||
It is possible to go through all triplets of
|
It is possible to go through all triplets of
|
||||||
input elements in $O(n^3)$ time.
|
the input elements in $O(n^3)$ time.
|
||||||
|
|
||||||
\item[$O(2^n)$]
|
\item[$O(2^n)$]
|
||||||
This time complexity often indicates that
|
This time complexity often indicates that
|
||||||
|
@ -285,9 +285,9 @@ of problems for which no polynomial algorithm is known.
|
||||||
|
|
||||||
\section{Estimating efficiency}
|
\section{Estimating efficiency}
|
||||||
|
|
||||||
By calculating the time complexity,
|
By calculating the time complexity of an algorithm,
|
||||||
it is possible to check before
|
it is possible to check before
|
||||||
implementing an algorithm that it is
|
implementing the algorithm that it is
|
||||||
efficient enough for the problem.
|
efficient enough for the problem.
|
||||||
The starting point for estimations is the fact that
|
The starting point for estimations is the fact that
|
||||||
a modern computer can perform some hundreds of
|
a modern computer can perform some hundreds of
|
||||||
|
@ -297,7 +297,7 @@ For example, assume that the time limit for
|
||||||
a problem is one second and the input size is $n=10^5$.
|
a problem is one second and the input size is $n=10^5$.
|
||||||
If the time complexity is $O(n^2)$,
|
If the time complexity is $O(n^2)$,
|
||||||
the algorithm will perform about $(10^5)^2=10^{10}$ operations.
|
the algorithm will perform about $(10^5)^2=10^{10}$ operations.
|
||||||
This should take at least some tens of seconds time,
|
This should take at least some tens of seconds,
|
||||||
so the algorithm seems to be too slow for solving the problem.
|
so the algorithm seems to be too slow for solving the problem.
|
||||||
|
|
||||||
On the other hand, given the input size,
|
On the other hand, given the input size,
|
||||||
|
@ -326,7 +326,7 @@ it is probably expected that the time
|
||||||
complexity of the algorithm is $O(n)$ or $O(n \log n)$.
|
complexity of the algorithm is $O(n)$ or $O(n \log n)$.
|
||||||
This information makes it easier to design the algorithm,
|
This information makes it easier to design the algorithm,
|
||||||
because it rules out approaches that would yield
|
because it rules out approaches that would yield
|
||||||
an algorithm with a slower time complexity.
|
an algorithm with a worse time complexity.
|
||||||
|
|
||||||
\index{constant factor}
|
\index{constant factor}
|
||||||
|
|
||||||
|
@ -412,9 +412,9 @@ the following subarray produces the maximum sum $10$:
|
||||||
\end{center}
|
\end{center}
|
||||||
\end{samepage}
|
\end{samepage}
|
||||||
|
|
||||||
\subsubsection{Solution 1}
|
\subsubsection{Algorithm 1}
|
||||||
|
|
||||||
A straightforward solution to the problem
|
A straightforward algorithm to the problem
|
||||||
is to go through all possible ways to
|
is to go through all possible ways to
|
||||||
select a subarray, calculate the sum of
|
select a subarray, calculate the sum of
|
||||||
numbers in each subarray and maintain
|
numbers in each subarray and maintain
|
||||||
|
@ -437,18 +437,18 @@ cout << p << "\n";
|
||||||
|
|
||||||
The code assumes that the numbers are stored in an array \texttt{x}
|
The code assumes that the numbers are stored in an array \texttt{x}
|
||||||
with indices $1 \ldots n$.
|
with indices $1 \ldots n$.
|
||||||
The variables $a$ and $b$ select the first and last
|
The variables $a$ and $b$ determine the first and last
|
||||||
number in the subarray,
|
number in the subarray,
|
||||||
and the sum of the subarray is calculated to the variable $s$.
|
and the sum of the numbers is calculated to the variable $s$.
|
||||||
The variable $p$ contains the maximum sum found during the search.
|
The variable $p$ contains the maximum sum found during the search.
|
||||||
|
|
||||||
The time complexity of the algorithm is $O(n^3)$,
|
The time complexity of the algorithm is $O(n^3)$,
|
||||||
because it consists of three nested loops and
|
because it consists of three nested loops and
|
||||||
each loop contains $O(n)$ steps.
|
each loop contains $O(n)$ steps.
|
||||||
|
|
||||||
\subsubsection{Solution 2}
|
\subsubsection{Algorithm 2}
|
||||||
|
|
||||||
It is easy to make the first solution more efficient
|
It is easy to make the first algorithm more efficient
|
||||||
by removing one loop from it.
|
by removing one loop from it.
|
||||||
This is possible by calculating the sum at the same
|
This is possible by calculating the sum at the same
|
||||||
time when the right end of the subarray moves.
|
time when the right end of the subarray moves.
|
||||||
|
@ -467,17 +467,17 @@ cout << p << "\n";
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
After this change, the time complexity is $O(n^2)$.
|
After this change, the time complexity is $O(n^2)$.
|
||||||
|
|
||||||
\subsubsection{Solution 3}
|
\subsubsection{Algorithm 3}
|
||||||
|
|
||||||
Surprisingly, it is possible to solve the problem
|
Surprisingly, it is possible to solve the problem
|
||||||
in $O(n)$ time, which means that we can remove
|
in $O(n)$ time, which means that we can remove
|
||||||
one more loop.
|
one more loop.
|
||||||
The idea is to calculate for each array position
|
The idea is to calculate for each array position
|
||||||
the maximum subarray sum that ends at that position.
|
the maximum sum of a subarray that ends at that position.
|
||||||
After this, the answer for the problem is the
|
After this, the answer for the problem is the
|
||||||
maximum of those sums.
|
maximum of those sums.
|
||||||
|
|
||||||
Condider the subproblem of finding the maximum subarray
|
Condider the subproblem of finding the maximum-sum subarray
|
||||||
that ends at position $k$.
|
that ends at position $k$.
|
||||||
There are two possibilities:
|
There are two possibilities:
|
||||||
\begin{enumerate}
|
\begin{enumerate}
|
||||||
|
@ -487,13 +487,13 @@ at position $k-1$, followed by the element at position $k$.
|
||||||
\end{enumerate}
|
\end{enumerate}
|
||||||
|
|
||||||
Our goal is to find a subarray with maximum sum,
|
Our goal is to find a subarray with maximum sum,
|
||||||
so in case 2 the subarray that ends at index $k-1$
|
so in case 2 the subarray that ends at position $k-1$
|
||||||
should also have the maximum sum.
|
should also have the maximum sum.
|
||||||
Thus, we can solve the problem efficiently
|
Thus, we can solve the problem efficiently
|
||||||
when we calculate the maximum subarray sum
|
when we calculate the maximum subarray sum
|
||||||
for each ending position from left to right.
|
for each ending position from left to right.
|
||||||
|
|
||||||
The following code implements the solution:
|
The following code implements the algorithm:
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
int p = 0, s = 0;
|
int p = 0, s = 0;
|
||||||
for (int k = 1; k <= n; k++) {
|
for (int k = 1; k <= n; k++) {
|
||||||
|
@ -508,7 +508,7 @@ that goes through the input,
|
||||||
so the time complexity is $O(n)$.
|
so the time complexity is $O(n)$.
|
||||||
This is also the best possible time complexity,
|
This is also the best possible time complexity,
|
||||||
because any algorithm for the problem
|
because any algorithm for the problem
|
||||||
has to access all array elements at least once.
|
has to examine all array elements at least once.
|
||||||
|
|
||||||
\subsubsection{Efficiency comparison}
|
\subsubsection{Efficiency comparison}
|
||||||
|
|
||||||
|
@ -524,7 +524,7 @@ measured.
|
||||||
|
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\begin{tabular}{rrrr}
|
\begin{tabular}{rrrr}
|
||||||
array size $n$ & solution 1 & solution 2 & solution 3 \\
|
array size $n$ & algorithm 1 & algorithm 2 & algorithm 3 \\
|
||||||
\hline
|
\hline
|
||||||
$10^2$ & $0{,}0$ s & $0{,}0$ s & $0{,}0$ s \\
|
$10^2$ & $0{,}0$ s & $0{,}0$ s & $0{,}0$ s \\
|
||||||
$10^3$ & $0{,}1$ s & $0{,}0$ s & $0{,}0$ s \\
|
$10^3$ & $0{,}1$ s & $0{,}0$ s & $0{,}0$ s \\
|
||||||
|
@ -539,8 +539,8 @@ The comparison shows that all algorithms
|
||||||
are efficient when the input size is small,
|
are efficient when the input size is small,
|
||||||
but larger inputs bring out remarkable
|
but larger inputs bring out remarkable
|
||||||
differences in running times of the algorithms.
|
differences in running times of the algorithms.
|
||||||
The $O(n^3)$ time solution 1 becomes slow
|
The $O(n^3)$ time algorithm 1 becomes slow
|
||||||
when $n=10^4$, and the $O(n^2)$ time solution 2
|
when $n=10^4$, and the $O(n^2)$ time algorithm 2
|
||||||
becomes slow when $n=10^5$.
|
becomes slow when $n=10^5$.
|
||||||
Only the $O(n)$ time solution 3 solves
|
Only the $O(n)$ time algorithm 3 processes
|
||||||
even the largest inputs instantly.
|
even the largest inputs instantly.
|
||||||
|
|
76
luku03.tex
76
luku03.tex
|
@ -4,22 +4,22 @@
|
||||||
|
|
||||||
\key{Sorting}
|
\key{Sorting}
|
||||||
is a fundamental algorithm design problem.
|
is a fundamental algorithm design problem.
|
||||||
In addition,
|
Many efficient algorithms
|
||||||
many efficient algorithms
|
|
||||||
use sorting as a subroutine,
|
use sorting as a subroutine,
|
||||||
because it is often easier to process
|
because it is often easier to process
|
||||||
data if the elements are in a sorted order.
|
data if the elements are in a sorted order.
|
||||||
|
|
||||||
For example, the question ''does the array contain
|
For example, the problem ''does the array contain
|
||||||
two equal elements?'' is easy to solve using sorting.
|
two equal elements?'' is easy to solve using sorting.
|
||||||
If the array contains two equal elements,
|
If the array contains two equal elements,
|
||||||
they will be next to each other after sorting,
|
they will be next to each other after sorting,
|
||||||
so it is easy to find them.
|
so it is easy to find them.
|
||||||
Also the question ''what is the most frequent element
|
Also the problem ''what is the most frequent element
|
||||||
in the array?'' can be solved similarly.
|
in the array?'' can be solved similarly.
|
||||||
|
|
||||||
There are many algorithms for sorting, that are
|
There are many algorithms for sorting, and they are
|
||||||
also good examples of algorithm design techniques.
|
also good examples of how to apply
|
||||||
|
different algorithm design techniques.
|
||||||
The efficient general sorting algorithms
|
The efficient general sorting algorithms
|
||||||
work in $O(n \log n)$ time,
|
work in $O(n \log n)$ time,
|
||||||
and many algorithms that use sorting
|
and many algorithms that use sorting
|
||||||
|
@ -99,15 +99,15 @@ is \key{bubble sort} where the elements
|
||||||
|
|
||||||
Bubble sort consists of $n-1$ rounds.
|
Bubble sort consists of $n-1$ rounds.
|
||||||
On each round, the algorithm iterates through
|
On each round, the algorithm iterates through
|
||||||
the elements in the array.
|
the elements of the array.
|
||||||
Whenever two consecutive elements are found
|
Whenever two consecutive elements are found
|
||||||
that are not in correct order,
|
that are not in correct order,
|
||||||
the algorithm swaps them.
|
the algorithm swaps them.
|
||||||
The algorithm can be implemented as follows
|
The algorithm can be implemented as follows
|
||||||
for array
|
for an array
|
||||||
$\texttt{t}[1],\texttt{t}[2],\ldots,\texttt{t}[n]$:
|
$\texttt{t}[1],\texttt{t}[2],\ldots,\texttt{t}[n]$:
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
for (int i = 1; i <= n; i++) {
|
for (int i = 1; i <= n-1; i++) {
|
||||||
for (int j = 1; j <= n-i; j++) {
|
for (int j = 1; j <= n-i; j++) {
|
||||||
if (t[j] > t[j+1]) swap(t[j],t[j+1]);
|
if (t[j] > t[j+1]) swap(t[j],t[j+1]);
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,7 @@ After the first round of the algorithm,
|
||||||
the largest element will be in the correct position,
|
the largest element will be in the correct position,
|
||||||
and in general, after $k$ rounds, the $k$ largest
|
and in general, after $k$ rounds, the $k$ largest
|
||||||
elements will be in the correct positions.
|
elements will be in the correct positions.
|
||||||
Thus, after $n$ rounds, the whole array
|
Thus, after $n-1$ rounds, the whole array
|
||||||
will be sorted.
|
will be sorted.
|
||||||
|
|
||||||
For example, in the array
|
For example, in the array
|
||||||
|
@ -267,7 +267,7 @@ algorithm that always swaps consecutive
|
||||||
elements in the array.
|
elements in the array.
|
||||||
It turns out that the time complexity
|
It turns out that the time complexity
|
||||||
of such an algorithm is \emph{always}
|
of such an algorithm is \emph{always}
|
||||||
at least $O(n^2)$ because in the worst case,
|
at least $O(n^2)$, because in the worst case,
|
||||||
$O(n^2)$ swaps are required for sorting the array.
|
$O(n^2)$ swaps are required for sorting the array.
|
||||||
|
|
||||||
A useful concept when analyzing sorting
|
A useful concept when analyzing sorting
|
||||||
|
@ -302,7 +302,7 @@ For example, in the array
|
||||||
\end{tikzpicture}
|
\end{tikzpicture}
|
||||||
\end{center}
|
\end{center}
|
||||||
the inversions are $(6,3)$, $(6,5)$ and $(9,8)$.
|
the inversions are $(6,3)$, $(6,5)$ and $(9,8)$.
|
||||||
The number of inversions indicates
|
The number of inversions tells us
|
||||||
how much work is needed to sort the array.
|
how much work is needed to sort the array.
|
||||||
An array is completely sorted when
|
An array is completely sorted when
|
||||||
there are no inversions.
|
there are no inversions.
|
||||||
|
@ -332,20 +332,20 @@ that is based on recursion.
|
||||||
Mergesort sorts a subarray \texttt{t}$[a,b]$ as follows:
|
Mergesort sorts a subarray \texttt{t}$[a,b]$ as follows:
|
||||||
|
|
||||||
\begin{enumerate}
|
\begin{enumerate}
|
||||||
\item If $a=b$, do not do anything because the subarray is already sorted.
|
\item If $a=b$, do not do anything, because the subarray is already sorted.
|
||||||
\item Calculate the index of the middle element: $k=\lfloor (a+b)/2 \rfloor$.
|
\item Calculate the position of the middle element: $k=\lfloor (a+b)/2 \rfloor$.
|
||||||
\item Recursively sort the subarray \texttt{t}$[a,k]$.
|
\item Recursively sort the subarray \texttt{t}$[a,k]$.
|
||||||
\item Recursively sort the subarray \texttt{t}$[k+1,b]$.
|
\item Recursively sort the subarray \texttt{t}$[k+1,b]$.
|
||||||
\item \emph{Merge} the sorted subarrays \texttt{t}$[a,k]$ and \texttt{t}$[k+1,b]$
|
\item \emph{Merge} the sorted subarrays \texttt{t}$[a,k]$ and \texttt{t}$[k+1,b]$
|
||||||
into a sorted subarray \texttt{t}$[a,b]$.
|
into a sorted subarray \texttt{t}$[a,b]$.
|
||||||
\end{enumerate}
|
\end{enumerate}
|
||||||
|
|
||||||
Mergesort is an efficient algorithm because it
|
Mergesort is an efficient algorithm, because it
|
||||||
halves the size of the subarray at each step.
|
halves the size of the subarray at each step.
|
||||||
The recursion consists of $O(\log n)$ levels,
|
The recursion consists of $O(\log n)$ levels,
|
||||||
and processing each level takes $O(n)$ time.
|
and processing each level takes $O(n)$ time.
|
||||||
Merging the subarrays \texttt{t}$[a,k]$ and \texttt{t}$[k+1,b]$
|
Merging the subarrays \texttt{t}$[a,k]$ and \texttt{t}$[k+1,b]$
|
||||||
is possible in linear time because they are already sorted.
|
is possible in linear time, because they are already sorted.
|
||||||
|
|
||||||
For example, consider sorting the following array:
|
For example, consider sorting the following array:
|
||||||
\begin{center}
|
\begin{center}
|
||||||
|
@ -466,7 +466,7 @@ when we restrict ourselves to sorting algorithms
|
||||||
that are based on comparing array elements.
|
that are based on comparing array elements.
|
||||||
|
|
||||||
The lower bound for the time complexity
|
The lower bound for the time complexity
|
||||||
can be proved by examining the sorting
|
can be proved by considering sorting
|
||||||
as a process where each comparison of two elements
|
as a process where each comparison of two elements
|
||||||
gives more information about the contents of the array.
|
gives more information about the contents of the array.
|
||||||
The process creates the following tree:
|
The process creates the following tree:
|
||||||
|
@ -599,10 +599,10 @@ corresponds to the following bookkeeping array:
|
||||||
\end{tikzpicture}
|
\end{tikzpicture}
|
||||||
\end{center}
|
\end{center}
|
||||||
|
|
||||||
For example, the value at index 3
|
For example, the value at position 3
|
||||||
in the bookkeeping array is 2,
|
in the bookkeeping array is 2,
|
||||||
because the element 3 appears 2 times
|
because the element 3 appears 2 times
|
||||||
in the original array (indices 2 and 6).
|
in the original array (positions 2 and 6).
|
||||||
|
|
||||||
The construction of the bookkeeping array
|
The construction of the bookkeeping array
|
||||||
takes $O(n)$ time. After this, the sorted array
|
takes $O(n)$ time. After this, the sorted array
|
||||||
|
@ -621,8 +621,8 @@ be used as indices in the bookkeeping array.
|
||||||
|
|
||||||
\index{sort@\texttt{sort}}
|
\index{sort@\texttt{sort}}
|
||||||
|
|
||||||
It is almost never a good idea to implement
|
It is almost never a good idea to use
|
||||||
an own sorting algorithm
|
a self-made sorting algorithm
|
||||||
in a contest, because there are good
|
in a contest, because there are good
|
||||||
implementations available in programming languages.
|
implementations available in programming languages.
|
||||||
For example, the C++ standard library contains
|
For example, the C++ standard library contains
|
||||||
|
@ -634,7 +634,7 @@ First, it saves time because there is no need to
|
||||||
implement the function.
|
implement the function.
|
||||||
In addition, the library implementation is
|
In addition, the library implementation is
|
||||||
certainly correct and efficient: it is not probable
|
certainly correct and efficient: it is not probable
|
||||||
that a home-made sorting function would be better.
|
that a self-made sorting function would be better.
|
||||||
|
|
||||||
In this section we will see how to use the
|
In this section we will see how to use the
|
||||||
C++ \texttt{sort} function.
|
C++ \texttt{sort} function.
|
||||||
|
@ -652,7 +652,7 @@ but a reverse order is possible as follows:
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
sort(v.rbegin(),v.rend());
|
sort(v.rbegin(),v.rend());
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
A regular array can be sorted as follows:
|
An ordinary array can be sorted as follows:
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
int n = 7; // array size
|
int n = 7; // array size
|
||||||
int t[] = {4,2,5,3,5,8,3};
|
int t[] = {4,2,5,3,5,8,3};
|
||||||
|
@ -667,7 +667,7 @@ Sorting a string means that the characters
|
||||||
in the string are sorted.
|
in the string are sorted.
|
||||||
For example, the string ''monkey'' becomes ''ekmnoy''.
|
For example, the string ''monkey'' becomes ''ekmnoy''.
|
||||||
|
|
||||||
\subsubsection{Comparison operator}
|
\subsubsection{Comparison operators}
|
||||||
|
|
||||||
\index{comparison operator}
|
\index{comparison operator}
|
||||||
|
|
||||||
|
@ -677,17 +677,17 @@ of the elements to be sorted.
|
||||||
During the sorting, this operator will be used
|
During the sorting, this operator will be used
|
||||||
whenever it is needed to find out the order of two elements.
|
whenever it is needed to find out the order of two elements.
|
||||||
|
|
||||||
Most C++ data types have a built-in comparison operator
|
Most C++ data types have a built-in comparison operator,
|
||||||
and elements of those types can be sorted automatically.
|
and elements of those types can be sorted automatically.
|
||||||
For example, numbers are sorted according to their values
|
For example, numbers are sorted according to their values
|
||||||
and strings are sorted in alphabetical order.
|
and strings are sorted in alphabetical order.
|
||||||
|
|
||||||
\index{pair@\texttt{pair}}
|
\index{pair@\texttt{pair}}
|
||||||
|
|
||||||
Pairs (\texttt{pair}) are sorted primarily by the first
|
Pairs (\texttt{pair}) are sorted primarily by their first
|
||||||
element (\texttt{first}).
|
elements (\texttt{first}).
|
||||||
However, if the first elements of two pairs are equal,
|
However, if the first elements of two pairs are equal,
|
||||||
they are sorted by the second element (\texttt{second}):
|
they are sorted by their second elements (\texttt{second}):
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
vector<pair<int,int>> v;
|
vector<pair<int,int>> v;
|
||||||
v.push_back({1,5});
|
v.push_back({1,5});
|
||||||
|
@ -700,7 +700,7 @@ $(1,2)$, $(1,5)$ and $(2,3)$.
|
||||||
|
|
||||||
\index{tuple@\texttt{tuple}}
|
\index{tuple@\texttt{tuple}}
|
||||||
|
|
||||||
Correspondingly, tuples (\texttt{tuple})
|
In a similar way, tuples (\texttt{tuple})
|
||||||
are sorted primarily by the first element,
|
are sorted primarily by the first element,
|
||||||
secondarily by the second element, etc.:
|
secondarily by the second element, etc.:
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
|
@ -741,7 +741,7 @@ struct P {
|
||||||
};
|
};
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
\subsubsection{Comparison function}
|
\subsubsection{Comparison functions}
|
||||||
|
|
||||||
\index{comparison function}
|
\index{comparison function}
|
||||||
|
|
||||||
|
@ -782,7 +782,7 @@ for (int i = 1; i <= n; i++) {
|
||||||
The time complexity of this approach is $O(n)$,
|
The time complexity of this approach is $O(n)$,
|
||||||
because in the worst case, it is needed to check
|
because in the worst case, it is needed to check
|
||||||
all elements in the array.
|
all elements in the array.
|
||||||
If the array can contain any elements,
|
If the array may contain any elements,
|
||||||
this is also the best possible approach, because
|
this is also the best possible approach, because
|
||||||
there is no additional information available where
|
there is no additional information available where
|
||||||
in the array we should search for the element $x$.
|
in the array we should search for the element $x$.
|
||||||
|
@ -791,7 +791,7 @@ However, if the array is \emph{sorted},
|
||||||
the situation is different.
|
the situation is different.
|
||||||
In this case it is possible to perform the
|
In this case it is possible to perform the
|
||||||
search much faster, because the order of the
|
search much faster, because the order of the
|
||||||
elements in the array guides us.
|
elements in the array guides the search.
|
||||||
The following \key{binary search} algorithm
|
The following \key{binary search} algorithm
|
||||||
efficiently searches for an element in a sorted array
|
efficiently searches for an element in a sorted array
|
||||||
in $O(\log n)$ time.
|
in $O(\log n)$ time.
|
||||||
|
@ -804,7 +804,7 @@ At each step, the search halves the active region in the array,
|
||||||
until the target element is found, or it turns out
|
until the target element is found, or it turns out
|
||||||
that there is no such element.
|
that there is no such element.
|
||||||
|
|
||||||
First, the search checks the middle element in the array.
|
First, the search checks the middle element of the array.
|
||||||
If the middle element is the target element,
|
If the middle element is the target element,
|
||||||
the search terminates.
|
the search terminates.
|
||||||
Otherwise, the search recursively continues
|
Otherwise, the search recursively continues
|
||||||
|
@ -823,7 +823,7 @@ while (a <= b) {
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
The algorithm maintains a range $a \ldots b$
|
The algorithm maintains a range $a \ldots b$
|
||||||
that corresponds to the active region in the array.
|
that corresponds to the active region of the array.
|
||||||
Initially, the range is $1 \ldots n$, the whole array.
|
Initially, the range is $1 \ldots n$, the whole array.
|
||||||
The algorithm halves the size of the range at each step,
|
The algorithm halves the size of the range at each step,
|
||||||
so the time complexity is $O(\log n)$.
|
so the time complexity is $O(\log n)$.
|
||||||
|
@ -856,7 +856,7 @@ if (t[k] == x) // x was found at index k
|
||||||
The variables $k$ and $b$ contain the position
|
The variables $k$ and $b$ contain the position
|
||||||
in the array and the jump length.
|
in the array and the jump length.
|
||||||
If the array contains the element $x$,
|
If the array contains the element $x$,
|
||||||
the index of the element will be in the variable $k$
|
the position of $x$ will be in the variable $k$
|
||||||
after the search.
|
after the search.
|
||||||
The time complexity of the algorithm is $O(\log n)$,
|
The time complexity of the algorithm is $O(\log n)$,
|
||||||
because the code in the \texttt{while} loop
|
because the code in the \texttt{while} loop
|
||||||
|
@ -866,7 +866,7 @@ is performed at most twice for each jump length.
|
||||||
|
|
||||||
In practice, it is seldom needed to implement
|
In practice, it is seldom needed to implement
|
||||||
binary search for searching elements in an array,
|
binary search for searching elements in an array,
|
||||||
because we can use the standard library instead.
|
because we can use the standard library.
|
||||||
For example, the C++ functions \texttt{lower\_bound}
|
For example, the C++ functions \texttt{lower\_bound}
|
||||||
and \texttt{upper\_bound} implement binary search,
|
and \texttt{upper\_bound} implement binary search,
|
||||||
and the data structure \texttt{set} maintains a
|
and the data structure \texttt{set} maintains a
|
||||||
|
@ -893,7 +893,7 @@ $\texttt{ok}(x)$ & \texttt{false} & \texttt{false}
|
||||||
\end{center}
|
\end{center}
|
||||||
|
|
||||||
\noindent
|
\noindent
|
||||||
The value $k$ can be found using binary search:
|
Now, the value $k$ can be found using binary search:
|
||||||
|
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
int x = -1;
|
int x = -1;
|
||||||
|
@ -947,7 +947,7 @@ for (int b = z; b >= 1; b /= 2) {
|
||||||
int k = x+1;
|
int k = x+1;
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
Note that unlike in the standard binary search,
|
Note that unlike in the ordinary binary search,
|
||||||
here it is not allowed that consecutive values
|
here it is not allowed that consecutive values
|
||||||
of the function are equal.
|
of the function are equal.
|
||||||
In this case it would not be possible to know
|
In this case it would not be possible to know
|
||||||
|
|
101
luku04.tex
101
luku04.tex
|
@ -30,7 +30,7 @@ size can be changed during the execution
|
||||||
of the program.
|
of the program.
|
||||||
The most popular dynamic array in C++ is
|
The most popular dynamic array in C++ is
|
||||||
the \texttt{vector} structure,
|
the \texttt{vector} structure,
|
||||||
that can be used almost like a regular array.
|
that can be used almost like an ordinary array.
|
||||||
|
|
||||||
The following code creates an empty vector and
|
The following code creates an empty vector and
|
||||||
adds three elements to it:
|
adds three elements to it:
|
||||||
|
@ -42,7 +42,7 @@ v.push_back(2); // [3,2]
|
||||||
v.push_back(5); // [3,2,5]
|
v.push_back(5); // [3,2,5]
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
After this, the elements can be accessed like in a regular array:
|
After this, the elements can be accessed like in an ordinary array:
|
||||||
|
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
cout << v[0] << "\n"; // 3
|
cout << v[0] << "\n"; // 3
|
||||||
|
@ -102,7 +102,7 @@ vector<int> v(10, 5);
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
The internal implementation of the vector
|
The internal implementation of the vector
|
||||||
uses a regular array.
|
uses an ordinary array.
|
||||||
If the size of the vector increases and
|
If the size of the vector increases and
|
||||||
the array becomes too small,
|
the array becomes too small,
|
||||||
a new array is allocated and all the
|
a new array is allocated and all the
|
||||||
|
@ -119,7 +119,7 @@ In addition, there is special syntax for strings
|
||||||
that is not available in other data structures.
|
that is not available in other data structures.
|
||||||
Strings can be combined using the \texttt{+} symbol.
|
Strings can be combined using the \texttt{+} symbol.
|
||||||
The function $\texttt{substr}(k,x)$ returns the substring
|
The function $\texttt{substr}(k,x)$ returns the substring
|
||||||
that begins at index $k$ and has length $x$,
|
that begins at position $k$ and has length $x$,
|
||||||
and the function $\texttt{find}(\texttt{t})$ finds the position
|
and the function $\texttt{find}(\texttt{t})$ finds the position
|
||||||
of the first occurrence of a substring \texttt{t}.
|
of the first occurrence of a substring \texttt{t}.
|
||||||
|
|
||||||
|
@ -196,14 +196,14 @@ for (auto x : s) {
|
||||||
}
|
}
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
An important property of a set is
|
An important property of sets
|
||||||
that all the elements are \emph{distinct}.
|
that all the elements are \emph{distinct}.
|
||||||
Thus, the function \texttt{count} always returns
|
Thus, the function \texttt{count} always returns
|
||||||
either 0 (the element is not in the set)
|
either 0 (the element is not in the set)
|
||||||
or 1 (the element is in the set),
|
or 1 (the element is in the set),
|
||||||
and the function \texttt{insert} never adds
|
and the function \texttt{insert} never adds
|
||||||
an element to the set if it is
|
an element to the set if it is
|
||||||
already in the set.
|
already there.
|
||||||
The following code illustrates this:
|
The following code illustrates this:
|
||||||
|
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
|
@ -214,13 +214,13 @@ s.insert(5);
|
||||||
cout << s.count(5) << "\n"; // 1
|
cout << s.count(5) << "\n"; // 1
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
C++ also has the structures
|
C++ also contains the structures
|
||||||
\texttt{multiset} and \texttt{unordered\_multiset}
|
\texttt{multiset} and \texttt{unordered\_multiset}
|
||||||
that work otherwise like \texttt{set}
|
that work otherwise like \texttt{set}
|
||||||
and \texttt{unordered\_set}
|
and \texttt{unordered\_set}
|
||||||
but they can contain multiple instances of an element.
|
but they can contain multiple instances of an element.
|
||||||
For example, in the following code all three instances
|
For example, in the following code all three instances
|
||||||
of the number 5 are added to the set:
|
of the number 5 are added to a multiset:
|
||||||
|
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
multiset<int> s;
|
multiset<int> s;
|
||||||
|
@ -231,7 +231,7 @@ cout << s.count(5) << "\n"; // 3
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
The function \texttt{erase} removes
|
The function \texttt{erase} removes
|
||||||
all instances of an element
|
all instances of an element
|
||||||
from a \texttt{multiset}:
|
from a multiset:
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
s.erase(5);
|
s.erase(5);
|
||||||
cout << s.count(5) << "\n"; // 0
|
cout << s.count(5) << "\n"; // 0
|
||||||
|
@ -249,7 +249,7 @@ cout << s.count(5) << "\n"; // 2
|
||||||
|
|
||||||
A \key{map} is a generalized array
|
A \key{map} is a generalized array
|
||||||
that consists of key-value-pairs.
|
that consists of key-value-pairs.
|
||||||
While the keys in a regular array are always
|
While the keys in an ordinary array are always
|
||||||
the consecutive integers $0,1,\ldots,n-1$,
|
the consecutive integers $0,1,\ldots,n-1$,
|
||||||
where $n$ is the size of the array,
|
where $n$ is the size of the array,
|
||||||
the keys in a map can be of any data type and
|
the keys in a map can be of any data type and
|
||||||
|
@ -259,11 +259,11 @@ C++ contains two map implementations that
|
||||||
correspond to the set implementations:
|
correspond to the set implementations:
|
||||||
the structure
|
the structure
|
||||||
\texttt{map} is based on a balanced
|
\texttt{map} is based on a balanced
|
||||||
binary tree and accessing an element
|
binary tree and accessing elements
|
||||||
takes $O(\log n)$ time,
|
takes $O(\log n)$ time,
|
||||||
while the structure
|
while the structure
|
||||||
\texttt{unordered\_map} uses a hash map
|
\texttt{unordered\_map} uses a hash map
|
||||||
and accessing an element takes $O(1)$ time on average.
|
and accessing elements takes $O(1)$ time on average.
|
||||||
|
|
||||||
The following code creates a map
|
The following code creates a map
|
||||||
where the keys are strings and the values are integers:
|
where the keys are strings and the values are integers:
|
||||||
|
@ -288,15 +288,15 @@ is added to the map.
|
||||||
map<string,int> m;
|
map<string,int> m;
|
||||||
cout << m["aybabtu"] << "\n"; // 0
|
cout << m["aybabtu"] << "\n"; // 0
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
The function \texttt{count} determines
|
The function \texttt{count} checks
|
||||||
if a key exists in the map:
|
if a key exists in a map:
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
if (m.count("aybabtu")) {
|
if (m.count("aybabtu")) {
|
||||||
cout << "key exists in the map";
|
cout << "key exists in the map";
|
||||||
}
|
}
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
The following code prints all keys and values
|
The following code prints all keys and values
|
||||||
in the map:
|
in a map:
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
for (auto x : m) {
|
for (auto x : m) {
|
||||||
cout << x.first << " " << x.second << "\n";
|
cout << x.first << " " << x.second << "\n";
|
||||||
|
@ -358,7 +358,7 @@ reverse(v.begin(), v.end());
|
||||||
random_shuffle(v.begin(), v.end());
|
random_shuffle(v.begin(), v.end());
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
These functions can also be used with a regular array.
|
These functions can also be used with an ordinary array.
|
||||||
In this case, the functions are given pointers to the array
|
In this case, the functions are given pointers to the array
|
||||||
instead of iterators:
|
instead of iterators:
|
||||||
|
|
||||||
|
@ -371,8 +371,8 @@ random_shuffle(t, t+n);
|
||||||
|
|
||||||
\subsubsection{Set iterators}
|
\subsubsection{Set iterators}
|
||||||
|
|
||||||
Iterators are often used when accessing
|
Iterators are often used to access
|
||||||
elements in a set.
|
elements of a set.
|
||||||
The following code creates an iterator
|
The following code creates an iterator
|
||||||
\texttt{it} that points to the first element in the set:
|
\texttt{it} that points to the first element in the set:
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
|
@ -421,11 +421,11 @@ if (it == s.end()) cout << "x is missing";
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
The function $\texttt{lower\_bound}(x)$ returns
|
The function $\texttt{lower\_bound}(x)$ returns
|
||||||
an iterator to the smallest element in the set
|
an iterator to the smallest element
|
||||||
whose value is \emph{at least} $x$, and
|
whose value is \emph{at least} $x$, and
|
||||||
the function $\texttt{upper\_bound}(x)$
|
the function $\texttt{upper\_bound}(x)$
|
||||||
returns an iterator to the smallest element
|
returns an iterator to the smallest element
|
||||||
in the set whose value is \emph{larger than} $x$.
|
whose value is \emph{larger than} $x$.
|
||||||
If such elements do not exist,
|
If such elements do not exist,
|
||||||
the return value of the functions will be \texttt{end}.
|
the return value of the functions will be \texttt{end}.
|
||||||
These functions are not supported by the
|
These functions are not supported by the
|
||||||
|
@ -487,18 +487,18 @@ cout << s[4] << "\n"; // 0
|
||||||
cout << s[5] << "\n"; // 1
|
cout << s[5] << "\n"; // 1
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
The benefit in using a bitset is that
|
The benefit in using bitsets is that
|
||||||
it requires less memory than a regular array,
|
they require less memory than ordinary arrays,
|
||||||
because each element in the bitset only
|
because each element in a bitset only
|
||||||
uses one bit of memory.
|
uses one bit of memory.
|
||||||
For example,
|
For example,
|
||||||
if $n$ bits are stored as an \texttt{int} array,
|
if $n$ bits are stored in an \texttt{int} array,
|
||||||
$32n$ bits of memory will be used,
|
$32n$ bits of memory will be used,
|
||||||
but a corresponding bitset only requires $n$ bits of memory.
|
but a corresponding bitset only requires $n$ bits of memory.
|
||||||
In addition, the values in a bitset
|
In addition, the values of a bitset
|
||||||
can be efficiently manipulated using
|
can be efficiently manipulated using
|
||||||
bit operators, which makes it possible to
|
bit operators, which makes it possible to
|
||||||
optimize algorithms.
|
optimize algorithms using bit sets.
|
||||||
|
|
||||||
The following code shows another way to create a bitset:
|
The following code shows another way to create a bitset:
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
|
@ -530,9 +530,9 @@ cout << (a^b) << "\n"; // 1001101110
|
||||||
|
|
||||||
A \texttt{deque} is a dynamic array
|
A \texttt{deque} is a dynamic array
|
||||||
whose size can be changed at both ends of the array.
|
whose size can be changed at both ends of the array.
|
||||||
Like a vector, a deque contains functions
|
Like a vector, a deque contains the functions
|
||||||
\texttt{push\_back} and \texttt{pop\_back}, but
|
\texttt{push\_back} and \texttt{pop\_back}, but
|
||||||
it also contains additional functions
|
it also contains the functions
|
||||||
\texttt{push\_front} and \texttt{pop\_front}
|
\texttt{push\_front} and \texttt{pop\_front}
|
||||||
that are not available in a vector.
|
that are not available in a vector.
|
||||||
|
|
||||||
|
@ -547,7 +547,7 @@ d.pop_front(); // [5]
|
||||||
\end{lstlisting}
|
\end{lstlisting}
|
||||||
|
|
||||||
The internal implementation of a deque
|
The internal implementation of a deque
|
||||||
is more complex than the implementation of a vector.
|
is more complex than that of a vector.
|
||||||
For this reason, a deque is slower than a vector.
|
For this reason, a deque is slower than a vector.
|
||||||
Still, the time complexity of adding and removing
|
Still, the time complexity of adding and removing
|
||||||
elements is $O(1)$ on average at both ends.
|
elements is $O(1)$ on average at both ends.
|
||||||
|
@ -583,7 +583,7 @@ provides two $O(1)$ time operations:
|
||||||
adding a element to the end of the queue,
|
adding a element to the end of the queue,
|
||||||
and removing the first element in the queue.
|
and removing the first element in the queue.
|
||||||
It is only possible to access the first
|
It is only possible to access the first
|
||||||
and the last element of a queue.
|
and last element of a queue.
|
||||||
|
|
||||||
The following code shows how a queue can be used:
|
The following code shows how a queue can be used:
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
|
@ -606,7 +606,7 @@ maintains a set of elements.
|
||||||
The supported operations are insertion and,
|
The supported operations are insertion and,
|
||||||
depending on the type of the queue,
|
depending on the type of the queue,
|
||||||
retrieval and removal of
|
retrieval and removal of
|
||||||
either the minimum element or the maximum element.
|
either the minimum or maximum element.
|
||||||
The time complexity is $O(\log n)$
|
The time complexity is $O(\log n)$
|
||||||
for insertion and removal and $O(1)$ for retrieval.
|
for insertion and removal and $O(1)$ for retrieval.
|
||||||
|
|
||||||
|
@ -623,7 +623,7 @@ As default, the elements in the C++
|
||||||
priority queue are sorted in decreasing order,
|
priority queue are sorted in decreasing order,
|
||||||
and it is possible to find and remove the
|
and it is possible to find and remove the
|
||||||
largest element in the queue.
|
largest element in the queue.
|
||||||
The following code shows an example:
|
The following code illustrates this:
|
||||||
|
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
priority_queue<int> q;
|
priority_queue<int> q;
|
||||||
|
@ -643,7 +643,8 @@ q.pop();
|
||||||
|
|
||||||
Using the following declaration,
|
Using the following declaration,
|
||||||
we can create a priority queue
|
we can create a priority queue
|
||||||
that supports finding and removing the minimum element:
|
that allows us to find and remove
|
||||||
|
the minimum element:
|
||||||
|
|
||||||
\begin{lstlisting}
|
\begin{lstlisting}
|
||||||
priority_queue<int,vector<int>,greater<int>> q;
|
priority_queue<int,vector<int>,greater<int>> q;
|
||||||
|
@ -670,20 +671,20 @@ and 9 belong to both of the lists.
|
||||||
A straightforward solution to the problem is
|
A straightforward solution to the problem is
|
||||||
to go through all pairs of numbers in $O(n^2)$ time,
|
to go through all pairs of numbers in $O(n^2)$ time,
|
||||||
but next we will concentrate on
|
but next we will concentrate on
|
||||||
more efficient solutions.
|
more efficient algorithms.
|
||||||
|
|
||||||
\subsubsection{Solution 1}
|
\subsubsection{Algorithm 1}
|
||||||
|
|
||||||
We construct a set of the numbers in $A$,
|
We construct a set of the numbers that appear in $A$,
|
||||||
and after this, we iterate through the numbers
|
and after this, we iterate through the numbers
|
||||||
in $B$ and check for each number if it
|
in $B$ and check for each number if it
|
||||||
also belongs to $A$.
|
also belongs to $A$.
|
||||||
This is efficient because the numbers in $A$
|
This is efficient because the numbers of $A$
|
||||||
are in a set.
|
are in a set.
|
||||||
Using the \texttt{set} structure,
|
Using the \texttt{set} structure,
|
||||||
the time complexity of the algorithm is $O(n \log n)$.
|
the time complexity of the algorithm is $O(n \log n)$.
|
||||||
|
|
||||||
\subsubsection{Solution 2}
|
\subsubsection{Algorithm 2}
|
||||||
|
|
||||||
It is not needed to maintain an ordered set,
|
It is not needed to maintain an ordered set,
|
||||||
so instead of the \texttt{set} structure
|
so instead of the \texttt{set} structure
|
||||||
|
@ -693,7 +694,7 @@ more efficient, because we only have to change
|
||||||
the underlying data structure.
|
the underlying data structure.
|
||||||
The time complexity of the new algorithm is $O(n)$.
|
The time complexity of the new algorithm is $O(n)$.
|
||||||
|
|
||||||
\subsubsection{Solution 3}
|
\subsubsection{Algorithm 3}
|
||||||
|
|
||||||
Instead of data structures, we can use sorting.
|
Instead of data structures, we can use sorting.
|
||||||
First, we sort both lists $A$ and $B$.
|
First, we sort both lists $A$ and $B$.
|
||||||
|
@ -707,12 +708,12 @@ so the total time complexity is $O(n \log n)$.
|
||||||
|
|
||||||
The following table shows how efficient
|
The following table shows how efficient
|
||||||
the above algorithms are when $n$ varies and
|
the above algorithms are when $n$ varies and
|
||||||
the elements in the lists are random
|
the elements of the lists are random
|
||||||
integers between $1 \ldots 10^9$:
|
integers between $1 \ldots 10^9$:
|
||||||
|
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\begin{tabular}{rrrr}
|
\begin{tabular}{rrrr}
|
||||||
$n$ & solution 1 & solution 2 & solution 3 \\
|
$n$ & algorithm 1 & algorithm 2 & algorithm 3 \\
|
||||||
\hline
|
\hline
|
||||||
$10^6$ & $1{,}5$ s & $0{,}3$ s & $0{,}2$ s \\
|
$10^6$ & $1{,}5$ s & $0{,}3$ s & $0{,}2$ s \\
|
||||||
$2 \cdot 10^6$ & $3{,}7$ s & $0{,}8$ s & $0{,}3$ s \\
|
$2 \cdot 10^6$ & $3{,}7$ s & $0{,}8$ s & $0{,}3$ s \\
|
||||||
|
@ -722,22 +723,22 @@ $5 \cdot 10^6$ & $10{,}0$ s & $2{,}3$ s & $0{,}9$ s \\
|
||||||
\end{tabular}
|
\end{tabular}
|
||||||
\end{center}
|
\end{center}
|
||||||
|
|
||||||
Solutions 1 and 2 are equal except that
|
Algorithm 1 and 2 are equal except that
|
||||||
they use different set structures.
|
they use different set structures.
|
||||||
In this problem, this choice has an important effect on
|
In this problem, this choice has an important effect on
|
||||||
the running time, because solution 2
|
the running time, because algorithm 2
|
||||||
is 4–5 times faster than solution 1.
|
is 4–5 times faster than algorithm 1.
|
||||||
|
|
||||||
However, the most efficient solution is solution 3
|
However, the most efficient algorithm is algorithm 3
|
||||||
that uses sorting.
|
that uses sorting.
|
||||||
It only uses half of the time compared to solution 2.
|
It only uses half of the time compared to algorithm 2.
|
||||||
Interestingly, the time complexity of both
|
Interestingly, the time complexity of both
|
||||||
solution 1 and solution 3 is $O(n \log n)$,
|
algorithm 1 and algorithm 3 is $O(n \log n)$,
|
||||||
but despite this, solution 3 is ten times faster.
|
but despite this, algorithm 3 is ten times faster.
|
||||||
This can be explained by the fact that
|
This can be explained by the fact that
|
||||||
sorting is a simple procedure and it is done
|
sorting is a simple procedure and it is done
|
||||||
only once at the beginning of solution 3,
|
only once at the beginning of algorithm 3,
|
||||||
and the rest of the algorithm works in linear time.
|
and the rest of the algorithm works in linear time.
|
||||||
On the other hand,
|
On the other hand,
|
||||||
solution 3 maintains a complex balanced binary tree
|
algorithm 3 maintains a complex balanced binary tree
|
||||||
during the whole algorithm.
|
during the whole algorithm.
|
Loading…
Reference in New Issue