From eed3cf095978215d7a6fe65ffbdd15b0dd24e5a0 Mon Sep 17 00:00:00 2001 From: Wang Lin <108318558+WangLin1126@users.noreply.github.com> Date: Mon, 25 Dec 2023 19:28:09 +0800 Subject: [PATCH 1/5] Add files via upload change line 1378 and 1380 of ln.tex about the stack and queue introduction --- ln.tex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ln.tex b/ln.tex index 14952e7..2780209 100644 --- a/ln.tex +++ b/ln.tex @@ -1375,9 +1375,9 @@ \section{堆栈和队列} 以上这些特异化的操作, 是为了堆栈和队列的特殊要求定制的. 堆栈和队列本质上都是表, 主要区别是 每一次 pop 删除或者 top/front 得到的元素, \begin{itemize} -\item {\bf 堆栈} 是最近一次 push 的元素, 先入先出(First in First Out, +\item {\bf 队列} 是最近一次 push 的元素, 先入先出(First in First Out, FIFO). -\item {\bf 队列} 是表中最早 push 的元素, 后入先出(Last in First Out, +\item {\bf 堆栈} 是表中最早 push 的元素, 后入先出(Last in First Out, LIFO). \end{itemize} From 07d5e29e012bffa58ef6c0e8ff74a6d5ba2aaf6f Mon Sep 17 00:00:00 2001 From: Wang Lin <108318558+WangLin1126@users.noreply.github.com> Date: Mon, 25 Dec 2023 19:33:03 +0800 Subject: [PATCH 2/5] Add files via upload change line 1378 and 1380 of ln.tex about the stack and queue introduction --- ln.tex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ln.tex b/ln.tex index 2780209..91d53e7 100644 --- a/ln.tex +++ b/ln.tex @@ -1375,10 +1375,10 @@ \section{堆栈和队列} 以上这些特异化的操作, 是为了堆栈和队列的特殊要求定制的. 堆栈和队列本质上都是表, 主要区别是 每一次 pop 删除或者 top/front 得到的元素, \begin{itemize} -\item {\bf 队列} 是最近一次 push 的元素, 先入先出(First in First Out, - FIFO). -\item {\bf 堆栈} 是表中最早 push 的元素, 后入先出(Last in First Out, +\item {\bf 堆栈} 是最近一次 push 的元素, 后入先出(Last in First Out, LIFO). +\item {\bf 队列} 是表中最早 push 的元素, 先入先出(First in First Out, + FIFO). \end{itemize} 显然, 队列这种特性是为了对排队建模. 比如我们有一台网络打印机, 然后一个 From 9836b458d479de5d6a8d8d76fe423066c3c45f51 Mon Sep 17 00:00:00 2001 From: Wang Lin <108318558+WangLin1126@users.noreply.github.com> Date: Wed, 27 Dec 2023 21:12:37 +0800 Subject: [PATCH 3/5] Add files via upload correct the process of finding the Kth largest element by heap and some mistakes --- ln.tex | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ln.tex b/ln.tex index 91d53e7..1659b82 100644 --- a/ln.tex +++ b/ln.tex @@ -1897,7 +1897,7 @@ \subsection{二叉搜索树, Binary Search Tree} 树代替 \verb|x|(如有). \item 节点 \verb|x| 有两棵子树, 则首先寻找 \verb|x| 的后继 \verb|y|, 这里指出 \verb|y| 必存在 (右子树的最小值), 然后用 \verb|y| 的值占据 - |verb|x| 的值, 再将 \verb|x->right| 中删除 \verb|y|. + \verb|x| 的值, 再将 \verb|x->right| 中删除 \verb|y|. \end{enumerate} 以上操作已经充分考虑了可以利用指针引用这样的神器. 在没有指针引用的年代, @@ -2608,13 +2608,13 @@ \subsection{C++ 的底层设计} 这个类和 \verb|SingleList| 类一样, 从 \verb|List
| 类派生, 它的节点 也从 \verb|Node
| 抽象类派生. 它的 \verb|insert| 和 \verb|remove| 和 \verb|SingleList| 的情形 -我们现在有: verb|BnaryTree|, -verb|BinarySearchTree|, \verb|AvlTree|, \verb|SplayTree|. +我们现在有: \verb|BnaryTree|, +\verb|BinarySearchTree|, \verb|AvlTree|, \verb|SplayTree|. 让我们来看看, 应该怎样理清它们的逻辑关系. 乍一看似乎并不复杂, \verb|AvlTree|, \verb|SplayTree| 都是一种二叉搜索 -树, 而 verb|BinarySearchTree| 是一种二叉树. 所以从 verb|BinaryTree| 派 +树, 而 \verb|BinarySearchTree| 是一种二叉树. 所以从 \verb|BinaryTree| 派 生出 \verb|BinarySearchTree|, 再从 \verb|BinarySearchTree| 派生出 \verb|AvlTree|, \verb|SplayTree|. 但稍微一想, 就会有问题, \verb|rotate| 这个操作, 在哪个类实现好? @@ -3203,7 +3203,7 @@ \subsection{堆算法的应用: 选择问题(rank $-k$ problem) } 素. 我知道你们在想啥, 但稍微有点节操的做法是, 先依次建一个大小为 $k$ 的堆. -从第 $k + 1$个元素起, 只有当新元素比根节点元素大时, 才插入堆. 直到全部 +从第 $k + 1$个元素起, 只有当新元素比根节点元素大时, 才插入堆, 每次插入后执行一次deleteMin. 确保堆的元素个数是$k$, 直到全部 元素插入完毕, 此时堆中的元素最小的就是根元素, 它之前必然有 $k - 1$ 个 元素比它大, 且未入堆的元素必然比它小. 因此它就是第 $k$ 大的元素. From e7779d043e830840f60e246eff23220db771bf25 Mon Sep 17 00:00:00 2001 From: Wang Lin <108318558+WangLin1126@users.noreply.github.com> Date: Fri, 29 Dec 2023 18:18:37 +0800 Subject: [PATCH 4/5] Add files via upload correct mistakes and clarify definition --- ln.tex | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/ln.tex b/ln.tex index 1659b82..091e7a3 100644 --- a/ln.tex +++ b/ln.tex @@ -3258,7 +3258,7 @@ \subsection{左堆(Leftist Heaps)} \item 比较 \verb|H1| 和 \verb|H2| 的 \verb|root|, 不妨设 \verb|H1| 的 根节点更小, 则将 \verb|merge(H2, H1.right)| 作为 \verb|H1| 的右子树. \item 如果 \verb|H1| 的右子树的 \verb|npl| 更长, 则交换 \verb|H1| 的左右子树. -\item 更新 verb|H1| 根节点的 \verb|npl|. +\item 更新 \verb|H1| 根节点的 \verb|npl|. \end{enumerate} 这是一个自然语言描述, 请对比课本 Fig 6.27 理解. @@ -3367,7 +3367,7 @@ \subsection{二项队列(Binomial Queues)} 而 \verb|deleteMin(H)| 操作, 则是先找到最小的 \verb|root|, 然后把其他 的子树全部并成 \verb|H'|(这个代价很低), 将最小 \verb|root| 所在子树, -除去 \verb|root| 之后的全部子数都并作 \verb|H''|(同样代价很低), 然后 +除去 \verb|root| 之后的全部子树都并作 \verb|H''|(同样代价很低), 然后 \verb|merge(H', H'')| 就可以了. 查找最小 \verb|root|, 建立 \verb|H'| 和 \verb|H''| 以及合并都是 $\Theta(\log n)$ 的动作,因此总代价是 $\Theta(\log n)$. @@ -3594,7 +3594,7 @@ \subsection{快速排序(Quicksort)} 快速排序, 同样采用了递归设计, 或者说, 在算法设计上, 采用了{\bf 分治策略} (devide-and-conquer, 老英国正米字旗了...). 其基本想法是, 把一个 规模为 $n$ 的问题, 切割成 $a$ 个独立的规模为 $\frac{n}{b}$ 的子问题. 逐 -个解决,再通过合并这 $a$ 个子问题的答案, 得到最终答案. *关键是:* 这个过 +个解决,再通过合并这 $a$ 个子问题的答案, 得到最终答案. {\bf 关键是:} 这个过 程会递归重复, 直到非常简单的基础情形, 能直接得到答案(或者说, 能够在 $\Theta(1)$ 时间得到答案.) @@ -3765,7 +3765,7 @@ \subsection{比较排序法的最优下界} 注意这里的证明用到了斯特林公式(Stirling's formula): $$ \lim_{n \to \infty} \frac{n!}{\sqrt{2 \pi n} - \left(\frac{n}{}\right)^n} = 1. + \left(\frac{n}{e}\right)^n} = 1. $$ 也就是本质上 $n!$ 是一个指数量: $$ @@ -3797,17 +3797,19 @@ \subsection{线性时间排序} countingsort(A[1...N], B[1...N], C[1...M]) for i = 1 to M C[i] = 0 - for i = 1 to n + for i = 1 to N C[A[i]] = C[A[i]] + 1 /// 依次计算每个 A[i] 的出现次数. - for i = 2 to k + for i = 2 to M C[i] = C[i] + C[i - 1] /// 计算 1...k 中每一个数最后一次在输出数组中出现的位置. - for i = n downto 1 - B[C[A[i]]] = A[i] /// B 存放输出数组. - C[A[i]] = C[A[i]] - 1 /// C 的递减次序保证了稳定性. + for i = N downto 1 + B[C[A[i]]--] = A[i] /// B 存放输出数组. + /// C 的递减次序保证了稳定性. \end{verbatim} -而桶排序可以看作是准备 \verb|M| 个容器, 比如数组 \verb|bucket[1...k]|, +而桶排序可以看作是准备 \verb|k| 个容器, 比如数组 \verb|bucket[1...k]|, +每个容器对应一定的范围, 其每一个元素都是数组或链表, 然后将 \verb|A[i]| 逐个 \verb|push_back| -到 \verb|bucket[A[i]]| 中去, 最后统一输出 \verb|bucket| 中全部元素. +到 \verb|bucket[A[i]]| 中去(\verb|push_back|的过程完成一次插入排序), 最后统一输出 \verb|bucket| 中全部元素. + 这两个算法都是非原地且稳定的. 看一下计数排序的例子. @@ -3864,7 +3866,7 @@ \subsubsection{最长子序列问题 (longest common subsequence, LCS)} 输出: 它们的 一个LCS. -例:$x =$ A, B, C, D 和 $y =$B, D, C, A, B, A. +例:$x =$ A, B, C, B, D, A, B 和 $y =$B, D, C, A, B, A. 则 LCS$(x, y) = $BDAB, BCAB, BCBA. 全部 LCS 是一个集合, 我们的算法要求返回其中一个就行. @@ -4209,7 +4211,7 @@ \subsection{拓扑序 (Topological Sort)} 因为寻找入度为 0 的顶点需要遍历, 是一个相对高代价的过程, 所以我们可以让 \verb|findNewVertexOFIndegreeZero| 总是返回全部入度为 0 的顶点, 并且依次插入一个队列, -然后我们在从这个队列弹出全部的拓扑序. +然后我们再从这个队列弹出全部的拓扑序. 这样能减少遍历次数. 具体过程参见 Fig. 9.6. 和 Fig. 9.7. Fig. 9.6 的第一列是初始的全部节点的入度. 然后入度为 $0$ 的节点就入队, @@ -4303,9 +4305,9 @@ \subsection{最短路径算法 (Shortest-Path Algorithms)} v.d = u.d + w(u, v) // u 和 v 之间的连接有没有使 v.d 更短. // 这就是三角不等式. \end{verbatim} -这个算法本身的叙述并不复杂. 我们看一下树上的例子 Fig 9.8. 也就是 Fig. 9.20. +这个算法本身的叙述并不复杂. 我们看一下书上的例子 Fig 9.8. 也就是 Fig. 9.20. Fig. 9.21 - Fig. 9.28 描述了一个从 $1$ 号顶点出发的单源最短路径的求解过程. -用的也是 Dijkstra 算法, 就是它描述的有点罗嗦, 我们重现一下过程. +用的也是 Dijkstra 算法, 就是它描述的有点啰嗦, 我们重现一下过程. 最初状态, $$ @@ -4313,7 +4315,7 @@ \subsection{最短路径算法 (Shortest-Path Algorithms)} $$ 然后 $$ -Q = \{(1, \infty), (2, \infty), (3, \infty), (4, \infty), (5, \infty), (6, \infty), (7, \infty)\}. +Q = \{(1, 0), (2, \infty), (3, \infty), (4, \infty), (5, \infty), (6, \infty), (7, \infty)\}. $$ 第一步, \verb|u = Q.pop_min()|, 显然, $u = 1$. 然后 \verb|u.adj()| 为 $$ @@ -4362,7 +4364,7 @@ \subsection{最短路径算法 (Shortest-Path Algorithms)} Q = \{(3, 3), (5, 3), (6, 9), (7, 5)\}. $$ 少了一个 $2$. 继续, 下一个出堆的可以是 $3$ 也可以是 $5$, 都可以, 这里 $3$ 先出 -(书上 $5$ 显出), 而 \verb|u.adj()| 为 +(书上 $5$ 先出), 而 \verb|u.adj()| 为 $$ \{1, 6\}, $$ @@ -4456,7 +4458,7 @@ \subsection{最短路径算法 (Shortest-Path Algorithms)} \noindent{\bf 定理} 对正边权图 $G = (V, E)$, Dijkstra 完成后, 有 $\forall v \in V$, $v.d = \delta(s, v)$. -\noindent{\bf 证明}: 先证, $\forall v in V$, 当 $v$ 从 $Q$ 中被弹出时, +\noindent{\bf 证明}: 先证, $\forall v \in V$, 当 $v$ 从 $Q$ 中被弹出时, $v.d = \delta(s, v)$. 假设不成立, 则存在第一个 $u \in V$, 当 $u$ 从 $Q$ 中弹出时, 由引理 I, 有 $$ From b4f98c5b79d4cc7628bd6d5ab148bd1fa12d5551 Mon Sep 17 00:00:00 2001 From: Wang Lin <108318558+WangLin1126@users.noreply.github.com> Date: Fri, 29 Dec 2023 21:21:57 +0800 Subject: [PATCH 5/5] Add files via upload --- ln.tex | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/ln.tex b/ln.tex index 091e7a3..8830264 100644 --- a/ln.tex +++ b/ln.tex @@ -4512,13 +4512,21 @@ \subsection{最短路径算法 (Shortest-Path Algorithms)} for each c in v.adj() if c.d = Inf c.d = v.d + 1; // 不会有其他形式的松弛, 因为权永远是 1. + Q.push(c); if c == u output(c.d); // 结束啦. end; \end{verbatim} -% 这里确 Bellman-Ford 和差分约束系统. - +% 这里缺 Bellman-Ford 和差分约束系统. +\hl{Bellman-Ford算法}\\ +Bellman-Ford算法简单地对所有边进行松弛操作,共执行$|V|-1$次。在重复地计算中,已计算得到正确的距离的边的数量不断增加,直到所有边都计算得到了正确的路径。\\ +每次循环操作实际上是对相邻节点的访问,第$n$次循环操作保证了所有深度为$n$的路径最短。由于图的最短路径最长不会经过超过 +$|V|-1$条边,所以可知Bellman-Ford算法所得为最短路径。\\ +\textbf{负边权操作}\\ +与Dijkstra算法不同的是,Dijkstra算法的基本操作“拓展”是在深度上寻路,而“松弛”操作则是在广度上寻路,这就确定了Bellman-Ford算法可以对负边进行操作而不会影响结果。\\ +\textbf{负权环判定}\\ +因为负权环可以无限制的降低总花费,所以如果发现第$n$次操作仍可降低花销,就一定存在负权环。 \subsection{全部点对之间的最短路径, All-pairs shortest paths} 即求图中任两点间的最短路径. 之前的 Dijkstra 和 Bellman-Ford 都是单源的. 朴素的算法是做 $n$ @@ -4767,7 +4775,7 @@ \subsection{全部点对之间的最短路径, All-pairs shortest paths} \begin{itemize} \item 输入: 无向连通图 $G = (V, E)$, 且有边权重 $w : E \to \mathbb{R}$. 这里假设各边的权重都是互异的. \item 输出: 最小生成树 $T$. - \item 最小生成树定义: 连通图 $G$ 的生成树是一棵连接了 $G$ 的全部顶点, 且有 + \item 最小生成树定义: 连通图 $G$ 的生成树是一棵连接了 $G$ 的全部顶点的树, 且有 $$ w(T) = \sum_{e \in T} w(e) $$ @@ -4816,7 +4824,8 @@ \subsection{全部点对之间的最短路径, All-pairs shortest paths} 而子问题的扩张过程仍然是贪婪策略, 或者说基于下面的定理: -\hl{定理} 令 $T$ 是 $G = (V, E)$ 的 MST, 令 $A \subset V$, 设 $(u, v)$ 连接 $A$ 和 +\hl{定理} 令 $T$ 是 $G = (V, E)$ 的 MST, 令 $A \subset V$, 设 $(u, v)\in E$ 连接 $A$ 和 +$V/A$连接起来的权重最小的边,则必有$( u , v ) \in T$ \bibliography{crazyfish.bib}