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