From dacd939a4d4666b6ba39698e71119dbfcc3c5446 Mon Sep 17 00:00:00 2001 From: Antti H S Laaksonen Date: Wed, 17 May 2017 23:30:18 +0300 Subject: [PATCH] Improve language --- chapter04.tex | 156 +++++++++++++++++++++++++------------------------- 1 file changed, 78 insertions(+), 78 deletions(-) diff --git a/chapter04.tex b/chapter04.tex index 34eea91..8f015a2 100644 --- a/chapter04.tex +++ b/chapter04.tex @@ -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::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 s; -s.push(3); -s.push(2); -s.push(5); -cout << s.front(); // 3 -s.pop(); -cout << s.front(); // 2 +queue 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,greater> 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 4–5 times faster than algorithm 1. +the running time, because Algorithm 2 +is 4–5 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.