Skip to content

Commit

Permalink
Updated crefs in ch11
Browse files Browse the repository at this point in the history
  • Loading branch information
liuxinyu95 committed Apr 20, 2023
1 parent 403683b commit 38805dd
Show file tree
Hide file tree
Showing 2 changed files with 18 additions and 18 deletions.
18 changes: 9 additions & 9 deletions datastruct/elementary/queue/queue-en.tex
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ \section{Introduction}
\section{Linked-list queue}
\index{Queue!linked-list}

We can insert or remove element from the head of a linked list. However, to support FIFO, we have to do one operation in head, and the other in tail. We need $O(n)$ time traverse to reach the tail, where $n$ is the length. To achieve the constant time performance goal, we use an extra variable to record the tail position, and apply a sentinel node $S$ to simplify the empty queue case handling, as shown in figure \cref{fig:empty-list}.
We can insert or remove element from the head of a linked list. However, to support FIFO, we have to do one operation in head, and the other in tail. We need $O(n)$ time traverse to reach the tail, where $n$ is the length. To achieve the constant time performance goal, we use an extra variable to record the tail position, and apply a sentinel node $S$ to simplify the empty queue case handling, as shown in \cref{fig:empty-list}.

\lstset{frame = single}
\begin{lstlisting}[language = Bourbaki]
Expand Down Expand Up @@ -90,7 +90,7 @@ \section{Linked-list queue}
\EndFunction
\end{algorithmic}

As the $S$ node is ahead of all other nodes, \textproc{Head} actually returns the next node to $S$, as shown in figure \cref{fig:list-queue}. It's easy to expand this implementation to concurrent environment with two locks on the head and tail respectively. $S$ node helps to prevent dead-lock when the queue is empty\cite{PODC96}\cite{SutterDDJ}.
As the $S$ node is ahead of all other nodes, \textproc{Head} actually returns the next node to $S$, as shown in \cref{fig:list-queue}. It's easy to expand this implementation to concurrent environment with two locks on the head and tail respectively. $S$ node helps to prevent dead-lock when the queue is empty\cite{PODC96}\cite{SutterDDJ}.

\begin{figure}[htbp]
\centering
Expand All @@ -102,7 +102,7 @@ \section{Linked-list queue}
\section{Circular buffer}
\index{Queue!Circular buffer}

Symmetrically, we can append element to the tail of array, but it takes linear time $O(n)$ to remove element from head. This is because we need shift all elements one cell ahead. The idea of circular buffer is to reuse the free cells before the first valid element after we remove elements from head, as shown figure \cref{fig:circular-buffer-queue}, and \cref{fig:circular-buffer}. We can use the head index, the length count, and the size of the array to define a queue. It's empty when the count is 0, it's full when count = size, we can also simplify the enqueue/dequeue implementation with modular operation.
Symmetrically, we can append element to the tail of array, but it takes linear time $O(n)$ to remove element from head. This is because we need shift all elements one cell ahead. The idea of circular buffer is to reuse the free cells before the first valid element after we remove elements from head, as shown \cref{fig:circular-buffer-queue,fig:circular-buffer}. We can use the head index, the length count, and the size of the array to define a queue. It's empty when the count is 0, it's full when count = size, we can also simplify the enqueue/dequeue implementation with modular operation.

\begin{figure}[htbp]
\centering
Expand Down Expand Up @@ -222,7 +222,7 @@ \section{Circular buffer}
\section{Paired-list queue}
\index{Queue!Paired-list queue}

We can access list head in constant time, but need linear time to access the tail. We can connect two lists `tail to tail' to implement queue, as shown in figure \cref{fig:horseshoe-magnet}. We define such queue as $(f, r)$, where $f$ is the front list, and $r$ is the rear list. The empty list is $([\ ], [\ ])$. We push new element to the head of $r$, and pop from the tail of $f$. Both are constant time.
We can access list head in constant time, but need linear time to access the tail. We can connect two lists `tail to tail' to implement queue, as shown in \cref{fig:horseshoe-magnet}. We define such queue as $(f, r)$, where $f$ is the front list, and $r$ is the rear list. The empty list is $([\ ], [\ ])$. We push new element to the head of $r$, and pop from the tail of $f$. Both are constant time.

\begin{figure}[htbp]
\centering
Expand Down Expand Up @@ -258,7 +258,7 @@ \section{Paired-list queue}

\index{Queue!Paired-array queue}

There is a symmetric implementation with a pair of arrays. Table \cref{tab:array-list-comp} shows the symmetric between list and array. We connect two arrays head to head to form a queue, as shown in figure \cref{fig:horseshoe-array}. When array $R$ becomes empty, we reverse array $F$ to replace $R$.
There is a symmetric implementation with a pair of arrays. \Cref{tab:array-list-comp} shows the symmetric between list and array. We connect two arrays head to head to form a queue, as shown in \cref{fig:horseshoe-array}. When array $R$ becomes empty, we reverse array $F$ to replace $R$.

\begin{table}[htbp]
\centering
Expand Down Expand Up @@ -326,7 +326,7 @@ \section{Balance Queue}
\label{eq:balance-invariant}
\ee

We check the lengths in every push/pop, however, it takes linear time to compute length. We can record the length in a variable, and update it during push/pop. Denote the paired-list queue as $(f, n, r, m)$, where $n = |f|$, $m = |r|$. From the balance rule (\cref{eq:balance-invariant}), we can check the length of $f$ to test if a queue is empty:
We check the lengths in every push/pop, however, it takes linear time to compute length. We can record the length in a variable, and update it during push/pop. Denote the paired-list queue as $(f, n, r, m)$, where $n = |f|$, $m = |r|$. From the balance rule \cref{eq:balance-invariant}, we can check the length of $f$ to test if a queue is empty:

\be
Q = \phi \iff n = 0
Expand Down Expand Up @@ -431,7 +431,7 @@ \section{Real-time queue}

Thanks to the balance rule. It means even repeat pushing as many elements as possible, from the previous to the next time when the queue is unbalanced, the $2n + 2$ steps are guaranteed to be completed, hence the new $f$ is ready. We can next safely start to compute $f' \doubleplus reverse\ r'$.

However, pop may happen before the completion of $2n + 2$ steps. We are facing the situation that needs extract element from $f$, while the new front list $f' = f \doubleplus reverse\ r$ hasn't been ready yet. To solve this issue, we duplicate a copy of $f$ when doing $reverse\ f$. We are save even repeat pop for $n$ times. Table \cref{tab:pop-before-n} shows the queue during phase 1 (reverse $f$ and $r$ in parallel)\footnote{Although it takes linear time to duplicate a list, however, the one time copying won't happen at all. We actually duplicate the reference to the front list, and delay the element level copying to each step}.
However, pop may happen before the completion of $2n + 2$ steps. We are facing the situation that needs extract element from $f$, while the new front list $f' = f \doubleplus reverse\ r$ hasn't been ready yet. To solve this issue, we duplicate a copy of $f$ when doing $reverse\ f$. We are save even repeat pop for $n$ times. \Cref{tab:pop-before-n} shows the queue during phase 1 (reverse $f$ and $r$ in parallel)\footnote{Although it takes linear time to duplicate a list, however, the one time copying won't happen at all. We actually duplicate the reference to the front list, and delay the element level copying to each step}.

\begin{table}[htbp]
\centering
Expand Down Expand Up @@ -527,10 +527,10 @@ \section{Lazy real-time queue}
\be
\begin{array}{cll}
& rotate\ (x \cons xs)\ (y \cons ys)\ a & \\
= & (x \cons xs) \doubleplus (reverse\ (y \cons ys)) \doubleplus a & \text{from (\cref{eq:rot-def})} \\
= & (x \cons xs) \doubleplus (reverse\ (y \cons ys)) \doubleplus a & \text{from \cref{eq:rot-def}} \\
= & x : (xs \doubleplus reverse\ (y \cons ys)) \doubleplus a) & \text{concatenation is associative} \\
= & x : (xs \doubleplus reverse\ ys \doubleplus (y \cons a)) & \text{reverse property, and associative} \\
= & x : rotate\ xs\ ys\ (y \cons a) & \text{reverse of (\cref{eq:rot-def})}
= & x : rotate\ xs\ ys\ (y \cons a) & \text{reverse of \cref{eq:rot-def}}
\end{array}
\ee

Expand Down
18 changes: 9 additions & 9 deletions datastruct/elementary/queue/queue-zh-cn.tex
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ \section{简介}
\section{列表实现}
\index{队列!单向链表实现}

我们可以用常数时间在列表头部插入、删除元素。但为了先进先出,我们只能在头部执行一种操作,而在尾部执行另一种操作。我们需要$O(n)$时间遍历整个列表以到达尾部,其中$n$是列表长度。这样就无法达到性能要求。为了解决这个问题,可以一个变量记录尾部位置。并用一个额外的节点$S$简化空队列的处理,如图\cref{fig:empty-list}所示。
我们可以用常数时间在列表头部插入、删除元素。但为了先进先出,我们只能在头部执行一种操作,而在尾部执行另一种操作。我们需要$O(n)$时间遍历整个列表以到达尾部,其中$n$是列表长度。这样就无法达到性能要求。为了解决这个问题,可以一个变量记录尾部位置。并用一个额外的节点$S$简化空队列的处理,\cref{fig:empty-list}所示。

\lstset{frame = single}
\begin{lstlisting}[language = Bourbaki]
Expand Down Expand Up @@ -90,7 +90,7 @@ \section{列表实现}
\EndFunction
\end{algorithmic}

$S$节点在所有其它节点的前面,\textproc{Head}实际返回$S$的下一个节点,如图\cref{fig:list-queue}所示。我们可以把这一实现扩展到并发环境。在头部和尾部各使用一把并发锁。$S$节点可以在队列空时避免死锁\cite{PODC96}、\cite{SutterDDJ}。
$S$节点在所有其它节点的前面,\textproc{Head}实际返回$S$的下一个节点,\cref{fig:list-queue}所示。我们可以把这一实现扩展到并发环境。在头部和尾部各使用一把并发锁。$S$节点可以在队列空时避免死锁\cite{PODC96}、\cite{SutterDDJ}。

\begin{figure}[htbp]
\centering
Expand All @@ -102,7 +102,7 @@ \section{列表实现}
\section{循环缓冲区}
\index{队列!循环缓冲区}

和列表相反,我们可以在常数时间将元素添加到数组末尾,但需要线性时间$O(n)$从头部删除。这是因为要将全部剩余元素依次向前移动。为了达到队列的性能要求,我们可以把数组的头尾连接起来,做成一个环,叫做循环缓冲区,如图如图\cref{fig:circular-buffer}、\cref{fig:circular-buffer-queue}所示。这样用数组的头部坐标head,队列长度count,和数组大小size,就可以完全表述队列。count等于0时队列为空,等于size时队列已满。我们还可以利用模运算简化入队、出队的实现。
和列表相反,我们可以在常数时间将元素添加到数组末尾,但需要线性时间$O(n)$从头部删除。这是因为要将全部剩余元素依次向前移动。为了达到队列的性能要求,我们可以把数组的头尾连接起来,做成一个环,叫做循环缓冲区,如图如\cref{fig:circular-buffer}、\cref{fig:circular-buffer-queue}所示。这样用数组的头部坐标head,队列长度count,和数组大小size,就可以完全表述队列。count等于0时队列为空,等于size时队列已满。我们还可以利用模运算简化入队、出队的实现。

\begin{figure}[htbp]
\centering
Expand Down Expand Up @@ -221,7 +221,7 @@ \section{循环缓冲区}
\section{双列表队列}
\index{队列!双列表队列}

列表的头部操作为常数时间,但尾部需要线性时间。我么可以把两个列表“尾对尾”连起来实现队列。形状类似一个马蹄形磁铁,如图\cref{fig:horseshoe-magnet}所示。两个列表分别叫做前(front)和后(rear)。队列记为$(f, r)$,空队列等于$([\ ], [\ ])$。我们把新元素加入$r$的头部,出队时,将元素从$f$的头部取走,性能都是常数时间。
列表的头部操作为常数时间,但尾部需要线性时间。我么可以把两个列表“尾对尾”连起来实现队列。形状类似一个马蹄形磁铁,\cref{fig:horseshoe-magnet}所示。两个列表分别叫做前(front)和后(rear)。队列记为$(f, r)$,空队列等于$([\ ], [\ ])$。我们把新元素加入$r$的头部,出队时,将元素从$f$的头部取走,性能都是常数时间。

\begin{figure}[htbp]
\centering
Expand Down Expand Up @@ -259,7 +259,7 @@ \section{双列表队列}
\ee

\index{队列!双数组队列}
我们可以用数组给出一个双列表的对称实现。利用表\cref{tab:array-list-comp}的对称性,我们将两个数组“头对头”连接起来形成队列,如图\cref{fig:horseshoe-array}所示。当$R$数组为空时,我们将$F$数组反转替换掉$R$数组。
我们可以用数组给出一个双列表的对称实现。利用\cref{tab:array-list-comp}的对称性,我们将两个数组“头对头”连接起来形成队列,\cref{fig:horseshoe-array}所示。当$R$数组为空时,我们将$F$数组反转替换掉$R$数组。

\begin{table}[htbp]
\centering
Expand Down Expand Up @@ -327,7 +327,7 @@ \section{平衡队列}
\label{eq:balance-invariant}
\ee

每次操作都需要检查长度,但这需要线性时间。为此我们将长度记录下来,并在出入队时更新。这样双列表队列就表示为$(f, n, r, m)$,其中$n = |f|$$m = |r|$,分别是两个列表的长度。根据平衡规则(\cref{eq:balance-invariant}),我们可以检查$f$的长度来判断队列是否为空:
每次操作都需要检查长度,但这需要线性时间。为此我们将长度记录下来,并在出入队时更新。这样双列表队列就表示为$(f, n, r, m)$,其中$n = |f|$$m = |r|$,分别是两个列表的长度。根据平衡规则\cref{eq:balance-invariant},我们可以检查$f$的长度来判断队列是否为空:

\be
Q = \phi \iff n = 0
Expand Down Expand Up @@ -431,7 +431,7 @@ \section{实时队列}

也就是说,从上次不平衡的时刻算起,即使不断持续入队新元素,以最快的速度再次使得队列不平衡时,$2n + 2$步计算恰好已经完成了。此时新的$f$列表被计算出来。我们可以安全地继续计算$f' \doubleplus reverse\ r'$。多亏了平衡规则,帮助我们保证了这一点。

但不幸的是,在$2n + 2$步计算完成前,出队操作可能随时发生。这会产生一个尴尬的情况:我们需要从$f$列表取出元素,但是新的$f$列表$f' = f \doubleplus reverse\ r$尚未计算好。此时没有一个可用的$f$列表。为了解决这个问题,我们在第一阶段并行计算$reverse\ f$时,另外保存一份$f$的副本。这样即使连续进行$n$次出队操作,我们仍然是安全的。表(\cref{tab:pop-before-n})给出了第一阶段逐步计算(同时反转$f$$r$)的某个时刻队列的样子\footnote{有人会产生疑问,通常复制一个列表需要花费和列表长度成比例的线性时间。这样整个方案就有问题了。实际上,这一线性时间的列表复制根本不会发生。我们复制的是$f$列表的引用。每个元素的复制被推迟到后续各个步骤中。}。
但不幸的是,在$2n + 2$步计算完成前,出队操作可能随时发生。这会产生一个尴尬的情况:我们需要从$f$列表取出元素,但是新的$f$列表$f' = f \doubleplus reverse\ r$尚未计算好。此时没有一个可用的$f$列表。为了解决这个问题,我们在第一阶段并行计算$reverse\ f$时,另外保存一份$f$的副本。这样即使连续进行$n$次出队操作,我们仍然是安全的。\cref{tab:pop-before-n}给出了第一阶段逐步计算(同时反转$f$$r$)的某个时刻队列的样子\footnote{有人会产生疑问,通常复制一个列表需要花费和列表长度成比例的线性时间。这样整个方案就有问题了。实际上,这一线性时间的列表复制根本不会发生。我们复制的是$f$列表的引用。每个元素的复制被推迟到后续各个步骤中。}。

\begin{table}[htbp]
\centering
Expand Down Expand Up @@ -528,10 +528,10 @@ \section{惰性实时队列}
\be
\begin{array}{cll}
& rotate\ (x \cons xs)\ (y \cons ys)\ a & \\
= & (x \cons xs) \doubleplus (reverse\ (y \cons ys)) \doubleplus a & \text{定义(\cref{eq:rot-def})} \\
= & (x \cons xs) \doubleplus (reverse\ (y \cons ys)) \doubleplus a & \text{定义\cref{eq:rot-def}} \\
= & x : (xs \doubleplus reverse\ (y \cons ys)) \doubleplus a) & \text{连接的结合性} \\
= & x : (xs \doubleplus reverse\ ys \doubleplus (y \cons a)) & \text{反转的性质和连接的结合性} \\
= & x : rotate\ xs\ ys\ (y \cons a) & \text{反向用定义(\cref{eq:rot-def})}
= & x : rotate\ xs\ ys\ (y \cons a) & \text{反向用定义\cref{eq:rot-def}}
\end{array}
\ee

Expand Down

0 comments on commit 38805dd

Please sign in to comment.