《算法分析与设计》学习笔记

作业占 \(20\%\),期中占 \(40\%\),项目(读 Paper,尝试实现或改进)占 \(40\%\)\(\newcommand{\de}{\delta}\newcommand{\al}{\mathcal}\newcommand{\fr}{\frac}\newcommand{\tio}{\tilde {\al O}}\)

Lecture 1

内容:Dijkstra,APASP,近似距离查询。

很多内容 xtq 在 WC2024 上讲过。

最短路问题

\(\de(s, t)\) 表示 \(s\)\(t\) 的最短路,满足三角形不等式。

使用斐波那契堆优化的 Dijkstra 的复杂度为 \(\mathcal{O}(m + n \log n)\)

对于 APSP,Floyd-Warshall 的复杂度为 \(\mathcal{O}(n ^ 3)\)。更优?无向无权图 \(\al{O}(n ^ {\omega})\approx \al{O}(n ^ {2.38})\),有向无权图 \(\mathcal{O}(n ^ {\mu})\approx \mathcal{O}(n ^ {2.53})\)。猜想:当边权是 \(1\sim n\) 的整数时,不存在 \(\mathcal{O}(n ^ {3 - \de})\) 的 APSP 算法。

无向无权图 APASP

\(d(u, v)\) 为近似距离。

  • stretch \(k\) 近似满足 \(\de(u, v) \leq d(u, v)\leq k\de(u, v)\)
  • surplus \(t\) 近似满足 \(\de(u, v) \leq d(u, v)\leq \de(u, v) + t\)

\(\tio(n ^ {2.5})\) surplus \(2\)

准备工作

独立随机以 \(\fr 1 k\) 的概率选中一个点,得到期望大小为 \(\al {O}(\fr n k)\) 的点集 \(D\)。对于度为 \(d\) 的点,其有 \(1 - (1 - \fr 1 k) ^ d\) 的概率和 \(D\) 相邻。取 \(d = ck\log n\),则概率为 \(1 - \fr 1 {n ^ c}\)

定理

给定度数 \(d\),可以找到大小为 \(\tio(\fr n d)\) 的点集 \(D\),满足所有度不小于 \(d\) 的点大概率和 \(D\) 相邻。

\(V_1\) 是所有度不小于 \(\sqrt n\) 的点,\(D_1\) 是大小为 \(\tio (\sqrt n)\) 的点集,满足 \(V_1\) 的每个点和 \(D_1\) 相邻。

\(E_2\) 是所有 \((u, v)\) 满足 \(u\notin V_1\)\(v\notin V_1\),则 \(\deg(u)\)\(\deg(v)\) 不超过 \(\sqrt n\)\(|E_2| = \al O(n\sqrt n)\)

算法流程

  • 从每个 \(u\in D_1\) 跑 BFS 求出 \(\de(u, v)\),其中 \(u\in D_1\)\(v\in V\)。时间 \(\al O(|D_1||E|) = \tio (n ^ 2\sqrt n)\)
  • 从每个 \(u\in V\)\(E_2\cup \de(D_1\times V)\) 上跑 Dijkstra。时间 \(\tio(n(|E_2| + n|D_1|)) = \tio(n ^ 2\sqrt n)\)

算法的时间复杂度 \(\tio(n ^ 2\sqrt n)\)

算法分析

  • \(u\in D_1\)\(v\in D_1\),则 \(\de(u, v)\) 在第一步求出。
  • \(u, v\) 最短路径上所有点的度小于 \(\sqrt n\),则每条边在 \(E_2\) 上,\(\de(u, v)\) 在第二步求出。
  • 若存在度不小于 \(\sqrt n\) 的点 \(w\),则 \(w\)\(w'\in D_1\) 相邻,\(d(u, v)\leq \de(u, w') + \de(w', v) \leq \de(u, v) + 2\)

通过 \(D_1\) 把路径上度数较大的点移动到较小的集合内。

\(\tio(n ^ {7 / 3})\) surplus \(2\)

这个算法还有可以利用的性质:最短路径要么只有 \(\de(D_1\times V)\),要么只有 \(E_2\),两部分独立。\(\delta(D_1\times V)\) 的部分已经是近似长度,但 \(E_2\) 的部分求出了精确值,这里还有优化空间。

改改阈值会发生什么?

\(V_1\) 是所有度不小于 \(n ^ {2 / 3}\) 的点,则 \(|D_1| = \tio(n ^ {1 / 3})\)。算法的第一步时间 \(\tio (n ^ {7 / 3})\),但 \(|E_2| = \al O(n ^ {5 / 3})\),无法 Dijkstra。这一步解决了 \(u\)\(v\) 度不小于 \(n ^ {2 / 3}\) 或最短路径上有度不小于 \(n ^ {2 / 3}\) 的点的情况,且 接下来只需考虑边集为 \(E_2\) 的情况,设新图为 \(G'\)

再分一层 \(V_2\) 表示所有度不小于 \(n ^ {1 / 3}\) 的点,则 \(|D_2| = \tio(n ^ {2 / 3})\)。设 \(V_2\) 对应 \(E_3\),则 \(|E_3| = \al{O}(n ^ {4 / 3})\)。这一步解决了最短路径上所有点度小于 \(n ^ {1 / 3}\) 的情况(实际上,没有两个相邻的点度均不小于 \(n ^ {1 / 3}\) 的情况)。

对于路径上有度在 \(n ^ {1 / 3}\)\(n ^ {2 / 3}\) 之间的点的情况,设 \(w\) 是最后一个这样的点,则 \(w\)\(v\) 都在 \(E_3\) 上,存在 \(w'\in D_2\)\(w\) 相邻。我们要求 \(u\)\(w'\) 之间在 \(G'\) 上的最短路 \(\de'(u, w')\)。这可以通过从 \(D_2\) 的每个点 BFS 得到,时间 \(\al{O}(|D_2||E_2|) = \tio(n ^ {7 / 3})\)

但这样还有一个问题,就是 \(\de'(D_2\times V)\) 的大小是 \(\tio(n ^ {5 / 3})\),不能直接丢进 Dijkstra。设 \(E ^ *\) 为每个 \(w\in V_2\) 和一个邻居 \(w'\in D_2\) 之间的边集,则 \(|E ^ *| = \al O(n)\)。将 \(E ^ *\) 丢入最终的 Dijkstra,这样就只要求最终能考虑到 \(\de'(\{u\}\times D_2)\)。所以从 \(u\) 跑 Dijkstra 的时候只要把 \(\de'(\{u\}\times D_2)\) 丢进去就行。

算法流程

  • 从每个 \(u\in D_1\) 跑 BFS 求出 \(\de(u, v)\),其中 \(u\in D_1\)\(v\in V\)。时间 \(\al O(|D_1||E|) = \tio (n ^ {7 / 3})\)
  • 从每个 \(u\in D_2\)\(E_2\) 上跑 BFS 求出 \(\de'(u, v)\),其中 \(u\in D_2\)\(v\in V\)。时间 \(\al O(|D_2||E_2|) = \tio (n ^ {7 / 3})\)
  • 从每个 \(u\in V\)\(E_3\cup \de(D_1\times V)\cup \de'(\{u\}\times D_2)\cup E ^ *\) 上跑 Dijkstra。时间 \(\tio(n(|E_3| + n|D_1| + |D_2| + n)) = \tio(n ^ {7 / 3})\)

通过 \(D_1\) 把路径上度数较大的点移动到较小的集合内,并删去 \(E_2\) 以外的所有边。

通过 \(D_2\) 把路径上度数适中的点移动到较小的集合内,只需保证最后一个度数适中的点被考虑到,并以此减少 Dijkstra 算法的边数。

算法分析

  • \(u\in D_1\)\(v\in D_1\),则 \(\de(u, v)\) 在第一步求出。
  • \(u, v\) 最短路径上所有点的度小于 \(n ^ {1 / 3}\),则每条边在 \(E_3\) 上,\(\de(u, v)\) 在第三步求出。
  • \(w\) 是路径上最后一个度在 \(n ^ {1 / 3}\)\(n ^ {2 / 3}\) 之间的点,\(w'\in D_2\)\((w, w')\in E ^ *\)。路径 \(\de'(u, w') + \de(w', w) + \de(w, v)\leq \de(u, v) + 2\) 且三部分分别属于 \(\de'(\{u\}\times D_2)\)\(E ^ *\)\(E_3\)

扩展

分更多层会出现更多中转点,可以做到 \(\tio(kn ^ {2 + \fr 1 {3k - 4}})\) 的无向无权图 surplus \(2(k - 1)\) APASP。当 \(k = \al O(\log n)\) 时,存在 \(\tio (n ^ 2)\) APASP。

无向无权图 surplus \(2\) APASP 在 2022 年被邓老师等人改进到了 \(\tio(n ^ {2.29})\)

\(\tio(n ^ {2.29})\) surplus \(2\)

普通 \(\min +\) 矩阵乘法(距离乘法)暂无 \(\al O(n ^ {3 - \de})\) 的算法,但 BD 矩阵(只需一个方向的 BD)存在 \(\tio(n ^ {2.687})\) 的距离乘法。

大概思想是观察到 \(\de(D_1\times V)\) 的部分只有两条边,相当于 \(\de(V\times D_1)\)\(\de(D_1\times V)\) 的距离乘法。用欧拉序将矩阵化为 BD,把这部分复杂度变优,就可以更改阈值做到更优复杂度了。

没讲怎么改进 \(\de(D_1\times V)\) 的求解。

分成 \(\al O(\log n)\) 层可以进一步做到 \(\al O(n ^ {2.2593})\)

\(\tio(n ^ 2)\ (2, 1)\) 近似

\(\de(u, v)\leq d(u, v)\leq 2\de(u, v) + 1\)

算法流程

对每个 \(0\leq i\leq \log_2 n\),设 \(V_i\) 为所有度不小于 \(\fr {n} {2 ^ i}\) 的点,得到大小为 \(\tio(2 ^ i)\) 的覆盖集 \(D_i\) 和对应的 \(E ^ *\)。设 \(E_i\) 为所有 \(u\notin V_{i - 1}\)\(v\notin V_{i - 1}\) 的边。

  • \(D_i\) 的每个点在 \(G_i = (V, E_i\cup E ^ *)\) 上跑 BFS 求出 \(\de_i(D_i\times V)\)。时间 \(\al O(|D_i|(|E_i| + n)) = \tio(n ^)\)

\(c_i(u)\)\(G_i\) 上距离 \(u\) 最近的 \(D_i\) 的点。

  • \(\de_i(u, c_i(u)) + \de_i(c_i(u), v)\)\(\de_i(u, c_i(v)) + \de_i(c_i(v), v)\) 更新 \(d(u, v)\)

算法分析

考虑 \(u, v\) 最短路上度数最大的点 \(x\)。因为 \(V_0 = \varnothing\),所以存在 \(i\) 使得 \(x\in V_i\)\(x\notin V_{i - 1}\)。此外,因为 \(x\) 的度数最大,所以路径上所有边都在 \(G_i\) 上。设 \(w\in D_i\)\(x\) 相邻且 \((w, x)\in E ^ *\),则 \(u\rightsquigarrow x\to w\)\(w\to x\rightsquigarrow v\) 都在 \(G_i\) 上。

\(u' = c_i(u)\)\(v' = c_i(v)\),于是 \(\de_i(u, u') \leq \de(u, x) + 1\)\(\de(v, v')\leq \de(v, x) + 1\),不妨设 \(\de_i(u, u') \leq \lfloor\fr {\de(u, v)} {2}\rfloor + 1\),则

\[\de_i(u, u') + \de_i(u', v) \leq 2\de_i(u, u') + \de_i(u, v) \leq 2\de(u, v) + 2 \]

  • 当路径长度是偶数时,如果 \(x\) 不在中点,不妨设靠近 \(u\) 一侧,那么 \(\de_i(u, u') + \de_i(u', v)\leq 2\de(u, v)\)。如果 \(x\) 在中点,那么考虑度数次大的点 \(x'\) 以及对应的 \(j\),根据 \(E_j\) 的定义可知路径上所有边在 \(E_j\) 上,此时 \(x'\) 一定不在中点。
  • 当路径长度是奇数时,\(\de_i(u, u') + \de_i(u', v) \leq 2\de(u, v) + 1\)。当 \(x\)\(x'\) 分别是最中间一条边的两端时,不等式可能取等。

为什么无向无权

对于有向图,以上算法显然行不通。

对于带权图,和一个点相邻不一定距离这个点近。可以考虑乘法近似。

无向带权图 APASP

截断 Dijkstra

考虑求出距离一个点最近的 \(b\) 个点,普通 Dijkstra 需要 \(\tio(n\cdot b)\) 的时间。截断 Dijkstra 在普通算法上加入改进:只考虑一个点权值最小的 \(b\) 条出边。

时间 \(\tio(b ^ 2)\)

\(\tio(n ^ {7 / 3})\) stretch \(3\)

类似的思想。

\(B(u)\) 是距离 \(u\) 最近的 \(n ^ {2 / 3}\) 个点,\(D\) 是随机选出的大小为 \(\tio(n ^ {1 / 3})\) 的点集,满足所有 \(B(u)\)\(D\) 有交。

这样,从 \(D\) 出发跑 Dijkstra,如果 \(u\in D\)\(v\in D\)\(v\in B(u)\),得到最短路。否则存在 \(w\in B(u)\cap D\)。因为 \(v\notin B(u)\),所以 \(\de(u, w)\leq \de(u, v)\)。同时 \(\de(w, v)\leq \de(w, u) + \de(u, v)\leq 2\de(u, v)\),所以 \(\de(u, w) + \de(w, v) \leq 3\de(u, v)\)

显然复杂度为 \(\tio(n ^ {7 / 3})\)

Cohen-Zwick 在 1997 年做到了 \(\tio(n ^ {3 / 2}m ^ {1 / 2})\) stretch \(2\)\(\tio(n ^ {7 / 3})\) stretch \(\fr 7 3\)\(\tio(n ^ 2)\) stretch \(3\)(可用 \(\tio(n ^ 2)\ (2, 1)\) 近似解决)。

近似最短路查询

以上讨论给出了使用 \(\al O(n ^ {5 / 3})\) 空间,\(\tio(n ^ {7 / 3})\) 预处理的 \(\al O(1)\) stretch \(3\) 近似最短路查询。

可以用 \(\al{O}(kn ^ {1 + \fr 1 k})\) 的空间做 \(\al O(k)\) stretch \(2k - 1\) 近似最短路查询 [Thorup, Zwick, 2001]。

Lecture 2

内容:二叉堆、二项树、斐波那契堆。

以下所有内容考虑小根堆:每个点的权值不大于其儿子权值。

二叉堆

堆的结构是一棵完全二叉树。根是最小值,树高 \(\log_2 n\)

如何实现?用指针维护父亲和儿子关系,或左儿子两倍右儿子两倍加一。

查询最小值

\(\al O(1)\)

插入

\(x\) 插入下一个叶子的位置,向上冒泡。\(\al O(\log n)\)

减法

将元素的值减去 \(k\),向上冒泡。\(\al O(\log n)\)

删除最小值

将根和最右侧叶子交换,向下冒泡(和较大的儿子交换)。\(\al O(\log n)\)

合并

暴力合并。\(\Omega(n)\)

二项堆

二项树

\(B_0\) 是一个点,\(B_k\)\(B_{k - 1}\) 在根底下接一棵 \(B_{k - 1}\)

性质:

  • \(|B_k| = 2 ^ k\)
  • \(B_k\) 深度为 \(k\)
  • \(B_k\) 的第 \(i\) 层有 \(\binom k i\) 个点。

定义一个 结点的阶 是其儿子个数,一棵 二项树的阶 是其根的阶。

原则:只合并同阶二项树

二项堆

二项堆维护的过程借助了二进制分组的思想保证复杂度:一旦有两棵同阶二项树,则合并。因此,若二项堆的大小为 \(n\),则该二项堆恰含所有 \(n\)\(2\) 进制下为 \(1\) 的位对应的二项树。

一个二项堆最多有 \(\lceil \log_2 n\rceil\) 棵二项树。

合并

模拟二进制加法。\(\al O(\log n)\)

删除最小值

找到最小值,删去。对应二项树分裂成若干二项树,与原有二项树合并。\(\al O (\log n)\)

减法

向上冒泡。\(\al O(\log n)\)

删除

减到 \(-\infty\) 再删除最小值。\(\al O(\log n)\)

插入

二进制模拟加 \(1\)\(\al O(\log n)\),均摊 \(\al O(1)\)

查询最小值

\(\al O(\log n)\)

斐波那契堆

斐波那契堆只有删除(最小值)的复杂度为 \(\al O (\log n)\)。其余操作均为均摊 \(\al O(1)\)

从空斐波那契堆开始,任何 \(a_1\) 次插入、\(a_2\) 次删除和 \(a_3\) 次减法的操作序列需要 \(\al O(a_1 + a_2\log n + a_3)\) 时间。

做到 \(\al O(1)\) 插入、删除和减法是不可能的,因为基于比较的排序有下界 \(\Omega(n\log n)\)。因此,斐波那契堆相当优秀。

其基本想法和二项堆类似,但对结构的要求更宽松。二项堆在每次操作后立刻重构,斐波那契堆采用 懒重构 的思想,直到删除最小值之后再重构。

维护:

  • 一些堆。
  • 根的列表和最小值指针。
  • 结点的标记:表示该结点已经失去了一个儿子。如果再失去一个,则以它为根的子树会被剪下来。

\(R(x)\)\(R(H)\) 表示阶,\(T(H)\) 表示树的个数,\(M(H)\) 表示被标记的结点数量。定义势能 \(\Phi(H) = 2T(H) + M(H)\)

定义均摊时间为实际用时加上势能的变化量,则总用时不超过总均摊时间之,因为势能非负且初始为 \(0\)。势能可能需要乘以一些常数。

插入

将结点插入根的列表,更新最小值指针。

时间 \(\al O(1)\),势能 \(+1\),均摊 \(\al O(1)\)

合并两棵树

将较大的根连到较小的根下面。

删除最小值

把根的所有子树全部融到根列表,重构使得没有根的阶相同。

时间 \(\al O(R(H) + T(H))\),势能 \(\al O(R(H')) - T(H)\),均摊(给势能乘以一定常数)\(\al O(R(H))\)

在只有前述操作时,\(R(H) = \al O(\log n)\)

减法

如果堆性质没有被破坏,则直接减。

否则把以 \(x\) 为根的树剪下,加入根列表,标记其父亲(除非其父亲为根)。

如果不特殊处理,这一步会扭曲树的形态,导致 \(R(H)\) 过大。为了保证树的平整,如果一个点已经被标记两次,则取消标记,并将以该点为根的子树剪下,标记其父亲(除非其父亲为根)。这一步可能递归。

设剪下子树的次数为 \(c\),则时间 \(\al O(c)\),势能 \(\al O(1) - c\),均摊 \(\al O(1)\)

分析

\(x\) 的儿子按照连边顺序记为 \(y_1, \cdots, y_k\)。当 \(y_i\) 连向 \(x\) 时,\(R(y_i)\) 等于当时的 \(R(x) \geq i - 1\)。而接下来 \(y_i\) 最多失去一个儿子。于是 \(R(y_i)\geq i - 2\)

\(F_k\)\(k\) 阶树的最小大小,则 \(F_1 = 1\)\(F_1 = 2\)\(F_k = F_{k - 2} + \cdots + F_0 + 1 = F_{k - 2} + F_{k - 1}\),这是斐波那契数列。于是 \(R(H)\leq \log_{\phi} n\)

合并

用双向列表维护根列表。

时间 \(\al O (1)\),势能 \(0\),均摊 \(\al O(1)\)

应用

\(\al O(m + n\log n)\) 最小生成树

在 Dijkstra 或 Prim 的过程中使用斐波那契堆,\(n\) 次插入,\(n\) 次删除和 \(m\) 次减法,时间 \(\al{O}(m + n\log n)\)

\(\al O(m + n\log ^ *n)\) 最小生成树

Fredman & Tarjan [1987] 通过限制堆的大小做到了更优的复杂度:考虑结合 Boruvka 和 Prim,从某个点开始,如果堆里有 \(k\) 个元素或碰到了当前生成森林,则停止本次 Prim,从另一个未被访问的点开始。一轮过后,将森林里的所有树缩点,则每个点至少 \(k\) 条出边,结点数量不超过 \(\fr {2m} k\)

\(k = 2 ^ {2m / n}\),则一轮复杂度 \(\al O(m + n\log k) = \al O(m)\)。新的 \(k' = 2 ^ {2m / n'} = 2 ^ {2 ^ {2m / n}}\)。可知总轮数为 \(\beta(m, n) = \min\{i: \log ^ {(i)} n \leq \fr m n\}\),其中 \(\log ^ {(i)} n\) 表示取 \(i\)\(\log\)

因此,当 \(m = \al O(n)\) 时,时间为 \(\al O(m \log ^ * n)\)。一旦存在常数 \(k\) 使得 \(m = n\log ^ {(k)} n\),则时间为 \(\al {O}(m)\)

目前学术界的 MST

随机算法 \(\al O(m)\) [Karger, Klein, Tarjan 1995]。

确定算法 \(\al O(m\alpha(m, n))\) [Chazalle 2000]。

最优算法(复杂度仍为 open problem) [Pettie, Ramachandran 2002]。

判定 MST \(\al O(m)\) [Dixon, Rauch, Tarjan 1992]。

Tarjan 老爷子是真牛啊。

posted @ 2024-09-12 17:43  qAlex_Weiq  阅读(1270)  评论(4编辑  收藏  举报