Uzi Vishkin. Deterministic Sampling–A New Technique for Fast Pattern Matching (1991)
Abstract. 本文是 https://doi.org/10.1137/0220002 的阅读笔记。文中提出了一种确定性的采样方法用于加速字符串匹配,由此得到了操作次数 $O(n)$ 时间 $O(\log^* n)$ 的并行算法。
文中用到了大量的分治、分块、复杂度平衡、递归的技巧,虽然这些无非是思考并行算法的时候的重点考虑方向,但第一次见的时候感觉还是比较奇妙,整个论文读完像做了一道超大的构造题。
简介
字符串匹配问题是一类常见的问题。我们重新叙述其严格定义和相关名词。
字符串匹配问题. 给定一个长度为 $n$ 的字符串 $T[1…n]$,称为文本串,和一个更短的长度为 $m$ 的字符串 $P[1…m]$,称为模式串。求所有的起始位置 $1\leq i\leq n-m+1$,满足 $P = T[i…i+m-1]$。
本文提出的是求解字符串匹配问题的并行算法,采用的计算模型是 CRCW PRAM,鉴于以前没有接触过,给出其定义:
定义 1.1(CRCW PRAM). CRCW PRAM 系指 Concurrent-Reading-Concurrent-Writing Parallel Random Access Machine,即多台处理器公用一个无限大容量的共享存储器,公用一个同步时钟,允许并发的读写操作。
必须详细考察写操作在并行时如何被采纳。CRCW PRAM 进一步可以分为
- Common CRCW PRAM 只允许多台处理器同时写相同内容到内存单元中,否则机器行为未定义。
- Arbitary CRCW PRAM 任选一台作为优胜者写入。
- Priority CRCW PRAM 选取编号最小的作为优胜者写入。
在涉及到细节时我们再深入讨论计算模型相关的问题。
此问题的朴素算法是 $O(nm)$ 的,容易发现在 有 $O(nm)$ 个处理器的 CRCW PRAM 上可以做到 $O(1)$ 时间。非平凡的算法如 KMP,Boyer Moore 等通常包含了两个阶段:模式分析和文本分析。KMP 中的求 next 就属于模式分析,把文本串放在上面跑就属于文本分析。
本文的主要结果是
- 一种新的字符串匹配问题的线性算法(串行)。
- 一种新的 $O(\log^* n)$ 文本分析的并行算法,处理器数最优。
- 一种需要先做模式分析的 $O(log^2 m / \log \log m)$ 文本分析算法,处理器数最优。
- 一种随机高概率成功的 $O(\log m)$ 的模式分析算法,处理器数最优。
- 一种非标准 PRAM 上的最优时间的文本分析算法,处理器数最优。
我们只试图看懂 1,2,3。
这里的 $\log^*$ 是一个没有见过的记号。这是一个增长极慢的函数:
定义 1.2. 我们定义 $\log^{(i)}$ 表示 $\log$ 函数的 $i$ 次复合。$\log^*(r)$ 定义为最小的 $i$ 使得 $\log^{(i)} \leq 2$。注意底数是 $2$。
这篇文章提出的核心科技 deterministic sampling 启发自我们如何“观察”一个模式串是否在文本串中出现:
- 采样. 首先找到一个模式串的局部。
- 搜索. 在主串中找所有的此局部出现的位置。
- 验证. 验证这个局部前后是否是模式串整体。
我们期望采到一个模式串的子集 $[s(1), …, s(l)]$,理想情况下其应当满足
- 其长度尽可能短,这样我们的搜索量才可能不会太大。
- 如果这个子集成功匹配,其前后一段距离都不可能产生合法的匹配,这样我们的验证量才可能不会太大。
这个采样并不能简单地用均匀随机构造,读者可以自行举出反例。但是却可以通过一个确定性的过程精细地构造,这就是所谓的 deterministic sample。
前置知识
字符串的周期性
定义 2.1.1. 令 $u, w$ 是两个字符串,称 $u$ 是 $w$ 的一个周期若 $w$ 是 $u^k$ 的一个前缀。
字符串 $w$ 的最短周期定义为最短的 $u$,$u$ 是 $w$ 的一个周期。若 $w$ 的最短周期长度不超过其本身长度的一半,称 $w$ 是周期性的。
收集一些我们熟知的 border 论。
命题 2.1.2(equivalence fact). 上述定义等价于 $w$ 是 $uw$ 的前缀。
命题 2.1.3(conflicting occurrence fact). 对于一个串 $w$,其最短周期是 $u$,若它出现在位置 $i$,则其不可能出现在位置 $j$,其中 $i < j < i + |u|$。
命题 2.1.4(gcd lemma / weak periodicity lemma). 若 $w$ 有长度为 $p, q$ 的周期,且 $p + q \leq |w|$,那么 $w$ 也有长度为 $\gcd(p, q)$ 的周期。
命题 2.1.5 若 $v$ 出现在位置 $j$ 和 $j + P$,那么
- $v$ 是周期性的,且其周期 $T$ 不超过 $P$。
- $v$ 出现在 $j + T$。
下面是本文使用的一个关键定义。
定义 2.1.5(witness 数组). 考虑一个非周期性的字符串 $P[1…m]$。对于任意的 $1 < i \leq m / 2$,都有 $m - i + 1$ 并非原串的 border。这表明,至少存在一个位置 $1\leq k\leq m - i + 1$ 使得 $P(k)\ne P(i + k - 1)$。$\mathrm{WITNESS}(i)$ 存储这样的一个 $k$。
此数组可以在 $O(\log m)$ [VI85] 甚至 $O(\log\log m)$ [BG88] 时间内求出(共 $O(m)$ 次操作),我们可能会找时间看比较简单的那一个。
CRCW PRAM 的选择
通过增加 $O(n)$ 个处理器,可以在 $O(1)$ 时间内用 Common CRCW PRAM 实现 Priority CRCW PRAM,增加后各种开销变化都是常数级别的。我们只需要考虑如下问题:
给定一个 $n$ 位长的 vector,求最左边的非零位。
原论文 reference 一篇八十年代打字机写的论文,不想看所以自己想一个。
解法.
首先考虑如何 $O(1)$ 时间 $O(m^2)$ 个处理器实现求长度为 $m$ 的 vector 的最小值。
第一步定义一个辅助数组 $g_{i, 0/1}$,全初始化为 $0$。给每个位分配 $m$ 个处理器,第 $i$ 位第 $j$ 个处理器用来比较 $v_i$ 是否小于等于 $v_j$。如果大于等于在 $g_{i, 1}$ 写 $1$,否则在 $g_{i, 0}$ 写 $1$。
第二步使用 $m$ 个处理器,如果仅 $g_{i, 1}$ 为一向返回值写 $v_i$。
然后我们只需要将数组分成 $\sqrt n$ 块,首先统计每块是否有数,然后用上述算法求出最小的有数的块,然后用上述算法求出有数的块中最左边的非零位。
本文中的所有操作可以在 Common CRCW PRAM 上完成。少数操作中最直观的算法需要使用 Priority CRCW PRAM,上述论证表明无额外开销地用 Common CRCW PRAM 实现。
其次,关于并行算法,我们有如下定理:
定理(Brent). 任何共 $x$ 次基本操作、总时间 $t$ 的同步并行算法(理解为可以同时执行任意数量的计算,但是必须按照依赖关系定时)可以用 $p$ 个处理器在 $\lceil x / p\rceil + t$ 时间内实现。
证明. 假设 $i$ 时刻同时执行的指令有 $x_i$ 条,这 $i$ 条可以被乱序执行,所以用 $p$ 个处理器执行时间为
$$
\sum_{i=1}^t \lceil x_i / p\rceil \leq \sum_{i=1}^t x_i / p + 1 \leq \lceil x / p\rceil + t
$$
此外可以想象如果有 $n$ 次计算,要求时间为 $t$,那么需要的处理器数至少是 $n / t$ 个。若算法达到了此下界(允许乘常数),则称处理器数最优。我们对上面的问题的解法是处理器数最优的。
前缀和问题
前缀和问题. 给定数组 $a[1…n]$,求数组 $b[1…n]$ 使得 $b[i] = \sum_{j=1}^i a[j]$。
这个问题好像其实没有那么平凡。[CV89] 指出存在一个 $O(\log n / \log\log n)$ 的算法,我们有空再看。
容易发现下面的问题弱于上面的问题:
给定一个 $0/1$ 数组 $a[1…n]$,提取其中标记为 $1$ 的编号到一个较短的数组中。
本文提出的算法经常使用前缀和算法来处理这个问题。
模式分析
模式分析的首要目标是构造 deterministic sample(DS)。这个采样的构造需要辅助问题 column sample。步骤 2 计算 column sample,步骤 3 用 column sample 构造 deterministic sample。
不失一般性地假设 $m$ 是偶数。然后考虑模式串的 $m / 2$ 个复制,按照下面形式排列:
$$
\begin{matrix}
\text{Copy $c_{m / 2}$} & & & & - & \cdots & - & - & \cdots & - \\
\vdots & & & & \vdots & & \vdots & \vdots & & \\
\text{Copy $c_2$} & & - & \cdots & - & \cdots & - & - & & \\
\text{Copy $c_1$} & - & - & \cdots & - & \cdots & - & & & \\
\text{Column id} & 1 & 2 & \cdots & m / 2 & \cdots & m & m + 1 & \cdots & m + m / 2 - 1 \\
\end{matrix}
$$
Column sample. 选择至多 $\log m - 1$ 个列 $\overline{ds}(1), …, \overline{ds}(l)$(其中 $l < \log m$),附上一个字符 $car(\overline{ds}(i))$,满足
- 恰好存在一个复制 $c_j$,满足
- $c_j$ 和所有选出的列都相交。即 $j\leq \overline{ds}(i) < j + m$。
- 相交处的字符恰好是附上的字符。即 $car(\overline{ds}(i)) = P(\overline{ds}(i) - j + 1)$。
- 对于其他的赋值,至少有一列与其相交,并且交点处的字符与列所附的字符不同。即对于任意的 $c_j\ne c_k$ 都存在 $\overline{ds}(i)$ 满足 $k\leq \overline{ds}(i) < k + m$,且 $P(\overline{ds}(i) - k + 1)\ne car(\overline{ds}(i))$。
其解的存在性由步骤 2 的算法给出。假设满足上述条件 $1$ 的复制为 $c_x$。
Deterministic sample. 无非是将 $\overline{ds}$ 用 $c_x$ 中的位置来表达。具体地,$DS$ 是一个数组 $[ds(1), ds(2), …, ds(l)]$,其中 $l\leq \log m - 1$,且 $ds(j) = \overline{ds}(j) - x + 1$。
步骤 1. 判断模式串是否是周期性的。同时求 witness 数组。如果是周期性的,求出周期。
注意若原串是周期性的(周期为 $p$),那么其长度为 $2p - 1$ 的前缀必然是非周期性的,在第 4.3 节我们讲如何做周期性的情况。
此过程可以被 [VI85] 完成,没有必要卷到 [BG88] 的水平,因为大头不在这里。
步骤 2. 此步骤归纳地构造集合 $A_1, …, A_l$,其中 $A_0 = \{c_1, …, c_{m / 2}\}$,且 $|A_{i + 1}|\leq |A_i| / 2$,$|A_l| = 1$。
下文中,对于复制 $c_i, c_j$,若 $i < j$,称 $c_i$ 在 $c_j$ 左边,$c_j$ 在 $c_i$ 右边。
若 $A_i$ 中只有一个元素,那么此步骤结束,我们求得了 $l = i$,可以转向步骤 3。
否则考虑 $A_i$ 中包含的复制中最左的和最右的,由于其差距不超过 $m / 2$,witness 数组给出了一个列,这个列上包含了两个不同的字符 $c, c’$。那么和此列处字符等于 $c$ 的复制和等于 $c’$ 的复制中至少有一类是不超过一半的,将不超过一半的一类对应的字符作为 $\overline{ds}(i)$,将该字符置为 $car(\overline{ds}(i))$,将该类作为 $A_{i + 1}$。
例子. 考虑字符串 $\texttt{100111}$,其排列为
$$
\begin{matrix}
c_3 & & & 1 & 0 & 0 & 1 & 1 & 1 & & \\
c_2 & & 1 & 0 & 0 & 1 & 1 & 1 & & & \\
c_1 & 1 & 0 & 0 & 1 & 1 & 1 & & & & \\
\end{matrix}
$$
考察 $\text{WITNESS}(2)$,这个值可能是 $1$,这给出了一个列,$c_1$ 和 $c_3$ 在此列上不同:
$$
\begin{matrix}
c_3 & & & \color{blue}{1} & 0 & 0 & 1 & 1 & 1 & & \\
c_2 & & 1 & \color{blue}{0} & 0 & 1 & 1 & 1 & & & \\
c_1 & 1 & 0 & \color{blue}{0} & 1 & 1 & 1 & & & & \\
\end{matrix}
$$
这列上为 $\texttt{0}$ 的有 $c_1, c_2$,为 $\texttt{1}$ 的有 $c_3$,后者大小不超过一半,将 $\overline{ds}(1)$ 置为 $3$,$car(\overline{ds}(1))$ 置为 $\texttt{0}$,$A_2$ 置为 $\{c_3\}$,继续递归。
$A_2$ 的大小为 $1$,本步骤完成。
可以发现这一步骤的核心就是选出某一位等于 $c$ 或者 $c’$ 的元素,因此每一轮迭代时间复杂度是 $O(\log |A_i| / \log \log |A_i|)$,总操作量是 $O(|A_i|)$,处理器数最优。集合的大小是指数级下降的,于是总复杂度是 $O(\log^2 m / \log \log m)$,总操作量是 $O(m)$。
步骤 3. 求出 DS。
本步骤是平凡的。
整个模式分析流程中的复杂度大头是步骤 2,总复杂度为 $O(\log^2 m / \log\log m)$。
基本文本分析
本节给出两种文本分析的算法,一种是基本常数时间算法,需要 $O(1)$ 时间 $O(n\log m)$ 次操作;另一种是基本最优加速算法,需要 $O(\log m)$ 时间 $O(n)$ 次操作。这两种算法展示了两种极端的情况,文章最后会将其平衡一个 $O(\log^* n)$ 时间 $O(n)$ 操作次数的算法。
首先假设模式串是非周期性的,稍后再谈如何处理周期性串的情形。
我们对串分块,块长为 $m/2$。如果把每个块和其后面地 $m/2$ 个元素视作一组资料,那么不同的组之间完全没有相互影响。下面的两个算法都是将所有块并行处理的。
基本常数时间算法
步骤 1. 对于位置 $1\leq i \leq n - m + 1$,逐一检查是否有 $T(i - 1 + ds(j)) = P(ds(j))$ 均成立。若是,称 $i$ 为候选。
显然此步骤可以在 $O(1)$ 时间,$O(n\log m)$ 次操作内完成。
步骤 2. 只保留每一块的第一个和最后一个候选。
此步骤可以在 $O(1)$ 时间,$O(n)$ 次操作内完成。
步骤 3. 对于保留的候选,逐字节验证。
此步骤可以在 $O(1)$ 时间,$O(n)$ 次操作内完成($n / (m / 2)\times m = O(n)$)
可能对于步骤 2,我们需要着重分析一下正确性。
命题(the ricochet property). 假设模式分析中得到的 $A_l$ 中的唯一元素是 $c_x$,那么若 $i$ 是候选,则其前面 $x - 1$ 个元素不可能是答案,其后面 $m / 2 - x$ 个元素不可能是答案。
证明. 反证,发现与 column sample 的定义矛盾。
发现第一个候选和最后一个候选联合起来可以排除中间所有的候选,因此只需要考虑这两个候选。
容易发现本算法可以在总共 $O(n\log m)$ 次操作,用 $O(n\log m)$ 个处理器可以在常数时间内完成。
基本最优加速算法
现在我们被允许总时间 $O(\log n)$,但是总操作次数限制为 $O(n)$。那么思路其实很自然,我们将上述全并行的算法改为增量地执行即可。需要点明的是,为了使得总操作次数为 $O(n)$,每一轮的候选位置数量必须至少折半。
依旧对于每一个块分开考虑。初始时块中所有位置都是候选。我们顺序地考虑 $ds(1), ds(2), …, ds(n)$,每次能够排除一些元素,但是这远远不够。因此我们继续用上 ricochet property,我们发现核对完 $ds(1)$ 之后,和最左边的侯选位置的相对位置不包含在 $A_1$ 中的都不可能成为答案,而 $A_1$ 的大小自然能够将候选位置的数量给 bound 住。
理顺思路之后我们考虑并行实现。
步骤 1. 计算所有满足如下条件的位置 $i$:$T(i - 1 + ds(1)) = P(ds(1))$。
步骤 2-1. 找到所有位置中最左边和最右边的一个,记为 $T(a)$,$T(b)$。定义左辅助变量 $lg = a-1 + ds(1)$ 和右辅助变量 $rg = a - 1 + ds(1)$。
现在用 $A_1$ 来构造集合 $T_{lg}$ 和 $T_{rg}$,使得 $\overline{ds}(1)$ 分别被平移至与 $lg$ 和 $rg$ 重合,其他元素可以根据此偏移量算出。
命题(key correctness observation). 不在 $T_{lg}\cup T_{rg}$ 中的元素不可能成为答案。
证明. 和 ricochet property 类似。
$T_{lg}\cup T_{rg}$ 中可能包含一些非候选的位置。将其中的候选位置称作真候选位置。
步骤 2-2. 用 $A_1$ 构造 $T_{lg}$ 和 $T_{rg}$,方法如上。
对于 $ds(\alpha), 2\leq \alpha \leq l$,容易将上述过程修改为:
步骤 2-1’. 计算 $T_{lg} \cup T_{rg}$ 中所有真候选位置中满足 $T(i - 1 + ds(\alpha))$ 最左边的和最右边的 $T(a), T(b)$,并重算 $lg, rg$。
步骤 2-2’. 用 $A_\alpha$ 更新 $T_{lg}, T_{rg}$。
这个过程需要仔细考虑处理器分配。为了做到最优处理器数,对于每一个块我们只有 $O(m/\log m)$ 个处理器。我们每一轮都只给 $T_{lg}$ 和 $T_{rg}$ 分配处理器,处理器将读取此位置是否为真候选,若不是则其将空转。然后发现前 $\log\log m$ 轮我们的处理器数是不够用的,但是这部分的总复杂度仍然是 $O(\log m)$ 的(因为每轮的处理时间指数级下降),后面 $\log m - \log \log m$ 轮处理器足够,每轮 $O(1)$,自然总复杂度是 $O(\log m)$,计算次数为 $O(m)$,处理器数最优。
步骤 3. 逐字符检查候选是否形成匹配。
综上所述,本算法复杂度为 $O(\log m)$,总操作数 $O(n)$,处理器数最优。
模式串有周期性的情况
不妨设模式串 $P = u^kv$,其中 $|u| = p$,$v$ 是 $u$ 的真前缀。
鉴于 $|v| < |u|$ 以及命题 2.1.3,算出 $u^k$ 的出现位置再验证后面 $|v|$ 位的复杂度正确,我们只需要先算出 $u^k$ 的出现位置。
首先用上述算法计算 $p’ = P[1, 2p - 1]$ 在 $T$ 中出现的位置。那么 $u^k$ 在某位置 $i$ 出现当且仅当 $p’$ 在 $i, i + p, …, i + (k - 2)p$ 均出现。上述条件 $\bmod p$ 同余的等价类上有一个连续的长度为 $k - 1$ 的区间。一切归结为如下问题:
哪些位置 $i$ 满足:从 $i$ 起,连续 $k - 1$ 位均为 1?
我们将串按长度 $k - 1$ 分块,则可以计算每块的第一个 $0$ 和最后一个 $0$(由此得到前缀 1 和后缀 1)的长度。现在对于位置为 $i$ 的 $1$,若其是后缀 1,且 $i + k - 2$ 打中了下一块的某个前缀 $1$,则其满足条件。于是上述过程可以在 $O(1)$ 时间、$O(n)$ 总操作内用最优处理器数完成。
复杂度平衡后的 $O(\log^* n)$ 文本分析
我们重新审视一下两个基本文本分析算法:
- 基本常数时间在一轮内并行验证所有的采样。
- 基本最优加速算法在 $O(\log n)$ 轮内逐步验证采样,每轮验证一个。
那么平衡的思路无非是,分成 $o(\log n)$ 轮逐步验证采样,每轮并行验证多个采样。需要注意的是,基本常数时间算法是带 $\log$ 的,所以需要预先像基本最优加速算法一样验证前几个元素。
我们的算法分为三个阶段:
阶段 1. 执行 $\log \log^* n + 1$ 轮基本最优加速算法。令 $\delta = \log\log^* n + 1$(现在已经处理了 $DS$ 的前几个元素)。
执行完上述操作后 $T_{lg}$ 和 $T_{rg}$ 中的元素个数上界为 $n / \log^* n$。
本阶段有 $O(n)$ 次操作,时间为 $O(\log \log^* n) < O(\log^* n)$。
阶段 2. 令 $count$ 从 $\log^*$ 递减到 $1$,每一轮用 $ds(\delta + 1), …, ds(\delta + \log^{(count)} n)$ 验证 $T_{lg}$ 和 $T_{rg}$ 中的真候选位置是否还是真候选。选出当前最左和最右的真候选,更新 $T_{lg}$ 和 $T_{rg}$。
每一轮我们会对每个候选位置执行 $O(\log^{(count)} n)$ 次验证操作,总时间为 $O(1)$,而候选位置的数量为 $n / 2^{\delta + \log^{(count)}n} = n / (\log^* n\log^{(count - 1)}n) \leq n / (\log^* n \log^{(count)} n)$,操作次数总共不超过 $O(n / \log^* n)$。所以阶段 2 的总时间为 $O(\log^* n)$,总操作次数为 $O(n)$。
阶段 3. 将候选位置前后文逐字符比较。
本阶段总操作次数 $O(n)$,时间为 $O(1)$。
综上,我们得到了总时间 $O(\log^* n)$,总操作次数为 $O(n)$,处理器数最优的文本分析算法。
进一步讨论
是否有亚线性匹配算法?
理论上很难。
是否能够向高维拓展?
虽然高维上没有类似于周期的概念,但是如果输入满足某种 conflict occurrence assumption 即任意将模式串摆在两个足够近的位置(每一维差距不超过 $m/2$)都可以发现重叠部分有一位不同(类似于 witness),做法就可以拓展到高维。总的来说难度很大。
是否能够处理近似匹配?
还是需要某种 conflict occurrence assumption,因此难度也很大。
参考文献
[VI85] Uzi Vishkin, Optimal parallel pattern matching in strings, Inform. and Control, 67 (1985), 91–113
[BG88] Dany Breslauer, Zvi Galil, An optimal $O(\log\log n)$ time parallel string matching algorithm, SIAM J. Comput., 19 (1990), 1051–1058
[CV89] Richard Cole, Uzi Vishkin, Faster optimal parallel prefix sums and list ranking, Inform. and Comput., 81 (1989), 334–352
[CV86] Cole, U. Vishkin, Deterministic coin tossing and accelerating cascades: micro and macro techniques for designing parallel algorithms, Proc. 18th Annual ACM Symposium on Theory of Computing, Association for Computing Machinery, New York, 1986, 206–219