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
|
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 4–5 times faster than algorithm 1.
|
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.
|
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.
|
||||||
|
|
Loading…
Reference in New Issue