Improve language
This commit is contained in:
parent
4dfe818c53
commit
dacd939a4d
156
chapter04.tex
156
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<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 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.
|
||||
|
|
Loading…
Reference in New Issue