Improve language

This commit is contained in:
Antti H S Laaksonen 2017-05-17 23:30:18 +03:00
parent 4dfe818c53
commit dacd939a4d
1 changed files with 78 additions and 78 deletions

View File

@ -147,26 +147,25 @@ insertion, search and removal.
The C++ standard library contains two set
implementations:
The structure \texttt{set} is based on a balanced
binary tree and the time complexity of its
operations is $O(\log n)$.
binary tree and its operations work in $O(\log n)$ time.
The structure \texttt{unordered\_set} uses hashing,
and the time complexity of its operations is $O(1)$ on average.
and its operations work in $O(1)$ time on average.
The choice of which set implementation to use
is often a matter of taste.
The benefit in the \texttt{set} structure
The benefit of the \texttt{set} structure
is that it maintains the order of the elements
and provides functions that are not available
in \texttt{unordered\_set}.
On the other hand, \texttt{unordered\_set} is
often more efficient.
On the other hand, \texttt{unordered\_set}
can be more efficient.
The following code creates a set
that consists of integers,
that contains integers,
and shows some of the operations.
The function \texttt{insert} adds an element to the set,
the function \texttt{count} returns the number of occurrences
of an element,
of an element in the set,
and the function \texttt{erase} removes an element from the set.
\begin{lstlisting}
@ -292,7 +291,7 @@ The function \texttt{count} checks
if a key exists in a map:
\begin{lstlisting}
if (m.count("aybabtu")) {
cout << "key exists in the map";
// key exists
}
\end{lstlisting}
The following code prints all the keys and values
@ -374,7 +373,7 @@ random_shuffle(t, t+n);
Iterators are often used to access
elements of a set.
The following code creates an iterator
\texttt{it} that points to the first element in a set:
\texttt{it} that points to the smallest element in a set:
\begin{lstlisting}
set<int>::iterator it = s.begin();
\end{lstlisting}
@ -397,16 +396,16 @@ Iterators can be moved using the operators
meaning that the iterator moves to the next
or previous element in the set.
The following code prints all the elements in the set:
The following code prints all the elements
in increasing order:
\begin{lstlisting}
for (auto it = s.begin(); it != s.end(); it++) {
cout << *it << "\n";
}
\end{lstlisting}
The following code prints the last element in the set:
The following code prints the largest element in the set:
\begin{lstlisting}
auto it = s.end();
it--;
auto it = s.end(); it--;
cout << *it << "\n";
\end{lstlisting}
@ -417,17 +416,19 @@ the iterator will be \texttt{end}.
\begin{lstlisting}
auto it = s.find(x);
if (it == s.end()) cout << "x is missing";
if (it == s.end()) {
// x is not found
}
\end{lstlisting}
The function $\texttt{lower\_bound}(x)$ returns
an iterator to the smallest element
an iterator to the smallest element in the set
whose value is \emph{at least} $x$, and
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$.
If such elements do not exist,
the return value of the functions will be \texttt{end}.
In both functions, if such an element does not exist,
the return value is \texttt{end}.
These functions are not supported by the
\texttt{unordered\_set} structure which
does not maintain the order of the elements.
@ -437,34 +438,32 @@ For example, the following code finds the element
nearest to $x$:
\begin{lstlisting}
auto a = s.lower_bound(x);
if (a == s.begin() && a == s.end()) {
cout << "the set is empty\n";
} else if (a == s.begin()) {
cout << *a << "\n";
} else if (a == s.end()) {
a--;
cout << *a << "\n";
auto it = s.lower_bound(x);
if (it == s.begin()) {
cout << *it << "\n";
} else if (it == s.end()) {
it--;
cout << *it << "\n";
} else {
auto b = a; b--;
if (x-*b < *a-x) cout << *b << "\n";
else cout << *a << "\n";
int a = *it; it--;
int b = *it;
if (x-b < a-x) cout << b << "\n";
else cout << a << "\n";
}
\end{lstlisting}
The code goes through all possible cases
using the iterator \texttt{a}.
The code assumes that the set is not empty,
and goes through all possible cases
using an iterator \texttt{it}.
First, the iterator points to the smallest
element whose value is at least $x$.
If \texttt{a} is both \texttt{begin}
and \texttt{end} at the same time, the set is empty.
If \texttt{a} equals \texttt{begin},
If \texttt{it} equals \texttt{begin},
the corresponding element is nearest to $x$.
If \texttt{a} equals \texttt{end},
the last element in the set is nearest to $x$.
If \texttt{it} equals \texttt{end},
the largest element in the set is nearest to $x$.
If none of the previous cases hold,
the element nearest to $x$ is either the
element that corresponds to $a$ or the previous element.
element that corresponds to \texttt{it} or the previous element.
\end{samepage}
\section{Other structures}
@ -487,7 +486,7 @@ cout << s[4] << "\n"; // 1
cout << s[5] << "\n"; // 0
\end{lstlisting}
The benefit in using bitsets is that
The benefit of using bitsets is that
they require less memory than ordinary arrays,
because each element in a bitset only
uses one bit of memory.
@ -529,7 +528,8 @@ cout << (a^b) << "\n"; // 1001101110
\index{deque}
A \key{deque} is a dynamic array
whose size can be changed at both ends of the array.
whose size can be efficiently
changed at both ends of the array.
Like a vector, a deque provides the functions
\texttt{push\_back} and \texttt{pop\_back}, but
it also provides the functions
@ -547,10 +547,10 @@ d.pop_front(); // [5]
\end{lstlisting}
The internal implementation of a deque
is more complex than that 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.
is more complex than that of a vector,
and for this reason, a deque is slower than a vector.
Still, both adding and removing
elements takes $O(1)$ time on average at both ends.
\subsubsection{Stack}
@ -587,13 +587,13 @@ and last element of a queue.
The following code shows how a queue can be used:
\begin{lstlisting}
queue<int> s;
s.push(3);
s.push(2);
s.push(5);
cout << s.front(); // 3
s.pop();
cout << s.front(); // 2
queue<int> q;
q.push(3);
q.push(2);
q.push(5);
cout << q.front(); // 3
q.pop();
cout << q.front(); // 2
\end{lstlisting}
\subsubsection{Priority queue}
@ -607,19 +607,19 @@ The supported operations are insertion and,
depending on the type of the queue,
retrieval and removal of
either the minimum or maximum element.
The time complexity is $O(\log n)$
for insertion and removal and $O(1)$ for retrieval.
Insertion and removal take $O(\log n)$ time,
and retrieval takes $O(1)$ time.
While an ordered set efficiently supports
all the operations of a priority queue,
the benefit in using a priority queue is
the benefit of 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.
balanced binary tree used in an ordered set.
\begin{samepage}
By default, the elements in the C++
By default, the elements in a C++
priority queue are sorted in decreasing order,
and it is possible to find and remove the
largest element in the queue.
@ -641,10 +641,10 @@ q.pop();
\end{lstlisting}
\end{samepage}
The following declaration
creates a priority queue
If we want to create a priority queue
that supports finding and removing
minimum elements:
the smallest element,
we can do it as follows:
\begin{lstlisting}
priority_queue<int,vector<int>,greater<int>> q;
@ -678,7 +678,7 @@ s.insert(3);
s.insert(7);
s.insert(9);
\end{lstlisting}
The speciality in this set is that we have access to
The speciality of this set is that we have access to
the indices that the elements would have in a sorted array.
The function $\texttt{find\_by\_order}$ returns
an iterator to the element at a given position:
@ -710,8 +710,8 @@ which may be hidden in their time complexities.
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 both contain $n$ elements.
Our task is to calculate the number of elements
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],\]
@ -719,24 +719,24 @@ the answer is 3 because the numbers 2, 5
and 9 belong to both of the lists.
A straightforward solution to the problem is
to go through all pairs of numbers in $O(n^2)$ time,
but next we will concentrate on
to go through all pairs of elements in $O(n^2)$ time,
but next we will focus on
more efficient algorithms.
\subsubsection{Algorithm 1}
We construct a set of the numbers that appear in $A$,
and after this, we iterate through the numbers
in $B$ and check for each number if it
We construct a set of the elements that appear in $A$,
and after this, we iterate through the elements
of $B$ and check for each elements if it
also belongs to $A$.
This is efficient because the numbers of $A$
This is efficient because the elements of $A$
are in a set.
Using the \texttt{set} structure,
the time complexity of the algorithm is $O(n \log n)$.
\subsubsection{Algorithm 2}
It is not needed to maintain an ordered set,
It is not necessary 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
@ -763,7 +763,7 @@ integers between $1 \ldots 10^9$:
\begin{center}
\begin{tabular}{rrrr}
$n$ & algorithm 1 & algorithm 2 & algorithm 3 \\
$n$ & Algorithm 1 & Algorithm 2 & Algorithm 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 \\
@ -776,19 +776,19 @@ $5 \cdot 10^6$ & $10{,}0$ s & $2{,}3$ s & $0{,}9$ s \\
Algorithms 1 and 2 are equal except that
they use different set structures.
In this problem, this choice has an important effect on
the running time, because algorithm 2
is 45 times faster than algorithm 1.
the running time, because Algorithm 2
is 45 times faster than Algorithm 1.
However, the most efficient algorithm is algorithm 3
However, the most efficient algorithm is Algorithm 3
which uses sorting.
It only uses half the time compared to algorithm 2.
It only uses half the time compared to Algorithm 2.
Interestingly, the time complexity of both
algorithm 1 and algorithm 3 is $O(n \log n)$,
but despite this, algorithm 3 is ten times faster.
Algorithm 1 and Algorithm 3 is $O(n \log n)$,
but despite this, Algorithm 3 is ten times faster.
This can be explained by the fact that
sorting is a simple procedure and it is done
only once at the beginning of algorithm 3,
only once at the beginning of Algorithm 3,
and the rest of the algorithm works in linear time.
On the other hand,
algorithm 1 maintains a complex balanced binary tree
Algorithm 1 maintains a complex balanced binary tree
during the whole algorithm.