形式陈述
读到一段文本的末尾时,一个固定模式与它“最接近”到了什么程度?如果忽略文本开头的无关部分,答案又会怎样变化?本页维护的是每个文本边界上的精确费用,不恢复匹配起点或编辑脚本。
设模式 P [ 0. . m ) 、文本 T [ 0. . n ) 在同一有限字母表上。插入、删除、替换各费用为 1 ,相等配对免费,不允许把交换邻字当作一次操作。使用单位编辑距离网格 理路 编辑距离的网格动态规划 Levenshtein distance dynamic programming · Wagner–Fischer algorithm 把插入、删除与替换写成前缀网格的三类边,求出最小改写费用并重建逐条可执行的脚本。 的内部递推
C [ i , j ] = min { C [ i − 1 , j ] + 1 , C [ i , j − 1 ] + 1 , C [ i − 1 , j − 1 ] + [ P [ i − 1 ] ≠ T [ j − 1 ] ] } . 固定一种边界模式,令 β ∈ { 0 , 1 } :
C [ i , 0 ] = i , C [ 0 , j ] = β j .
global,β = 1 : C [ m , j ] = d ( P , T [ 0. . j ) ) ,即模式与整个已读前缀的距离。
infix,β = 0 : C [ m , j ] = min 0 ≤ s ≤ j d ( P , T [ s . . j ) ) 。文本前缀可以免费略过,候选后缀必须结束在当前边界;也允许 s = j 的空后缀。
第二种含义可从网格直接证明:第零行任意位置都可作为零费用起点,之后按同样三类边改写模式。取所有起点的最小值,恰好得到上述最小后缀距离。这不是允许任意丢弃模式前后两端的局部比对。
对 m > 0 ,令第 i − 1 位对应第 i 行,用两个 m 位向量保存垂直差分:
( P v ) i − 1 = [ C [ i , j ] − C [ i − 1 , j ] = 1 ] , ( M v ) i − 1 = [ C [ i , j ] − C [ i − 1 , j ] = − 1 ] . 另存末行分数 S = C [ m , j ] 。初始 P v 的全部有效位为一,M v = 0 , S = m 。差分为零的位置两个位都为零;同一位置不能同时置一。
像Shift-And 的字符掩码 理路 Shift-And 字符类流式匹配 Shift-And character-class matching · Shift-And algorithm · 字符类位并行匹配 将每个仍然匹配的模式前缀编码为一位,以跨字移位和字符类掩码在线报告全部重叠出现位置。 那样,预处理 E ( c ) ,在 P [ i ] = c 的位置置一。本页参考程序只接收字面字节模式;相似的掩码外形不意味着两算法的状态相同。每读符号 c ,设 E = E ( c ) ,依次计算
X v = E | M v , X h = ( ( ( E & P v ) + P v ) ⊕ P v ) | E , P h = M v | ¬ ( X h | P v ) , M h = P v & X h . 这里 ⊕ 是按位异或。P h , M h 编码新旧列之间的水平差分,尚未移位时先更新
S ← S + ( P h ) m − 1 − ( M h ) m − 1 . 再令 P ^ h = ( P h ≪ 1 ) | β 、M ^ h = M h ≪ 1 ,得到下一列
P v ′ = M ^ h | ¬ ( X v | P ^ h ) , M v ′ = P ^ h & X v . 全部向量只保留低 m 位,加法按模 2 m 理解。一个字装不下时,以 q = ⌈ m / w ⌉ 个字模拟这些操作;加法进位和移位进位都必须跨字传递 。这是Word-RAM 理路 Word-RAM 模型 Word RAM · Word-RAM model 以 w 位机器字、常数时间随机访存和明确字级操作集分析算法的随机访问机模型。 上的 O ( q ) 次字操作,不是任意精度整数上的无条件常数时间。
直觉
数值很大,相邻差却只有三种
单位编辑操作保证相邻两行、相邻两列的值相差至多一。多一个模式字符,至多多付一次删除;反过来删掉这个字符也至多改变一次费用。文本多一个字符时同理。自由前缀模式取所有后缀的最小值:非空最优后缀去掉最后字符仍是上一边界的候选;若最优后缀为空,其费用为 i ,而上一边界也可选空后缀。两个方向的界仍成立。
所以垂直、水平差分都属于 { − 1 , 0 , 1 } 。从第零行的已知值出发累加垂直差分,可以重建整列。扫描不必每步实际累加它们,因为末行分数只需加上新一列最下面的水平差分。这也是必须在移位前检查第 m − 1 位的原因。
从一个格子推到整列
取一个格子的左上角值为 a ,左下角为 a + v ,右上角为 a + h ,相等标记为 e ∈ { 0 , 1 } 。令 t = min { − e , v , h } ,右下角按网格递推等于 a + 1 + t 。因此输出差分为
v ′ = 1 + t − h , h ′ = 1 + t − v . 输入只有 3 × 3 × 2 = 18 种。把正、负差分分别编码成两个布尔量,逐项代入上式,可以得到:令 x v = e ∨ [ v = − 1 ] ,则
[ v ′ = 1 ] = [ h = − 1 ] ∨ ¬ ( x v ∨ [ h = 1 ] ) , [ v ′ = − 1 ] = [ h = 1 ] ∧ x v . 交换横竖角色,就得到 P h , M h 的对应公式。参考程序把全部18种输入逐一与数值递推核对。整列先由旧垂直差分求出水平差分,再把水平差分向下一行移动,转成新垂直差分。第零行的水平差分恰是 β ,所以移入 P ^ h 最低位的是 β ,而不是一条不问边界的固定常数。
一次加法为何能解决行间依赖
难点在于第 i 位所需的 X h 还依赖前一行的负水平差分。用从零开始的位下标,记旧 P v 的位为 p i 、相等位为 e i ,则需要的传播规则是
x i = e i ∨ c i , c 0 = 0 , c i + 1 = p i ∧ ( e i ∨ c i ) . 这恰好是一条二进制进位链。把 P v 与 E & P v 相加:第二个加数的位 e i p i 不会在 p i = 0 处产生进位,故加法输出进位就是上式的 c i + 1 。该位和为 p i ⊕ ( e i p i ) ⊕ c i ;再异或 p i 并或上 e i ,当 e i = 1 时得到一,当 e i = 0 时得到 c i ,结果正是 x i 。
于是算术加法同时完成了这条有限状态传播。这里需要 进位沿连续正差分传播;不能套用“并行字段之间总要隔离进位”的直觉把它截断。最高位之外的进位可以丢弃,因为它不再影响任何有效行。
这三个层次连成归纳证明:初始差分正确;逐格布尔式等价于网格递推;进位链一次算出所有需要的横向条件;移位注入正确边界后得到下一列,末行分数也同步更新。因而每一步返回的都是所声明模式的精确费用。
图片加载失败 差分列、跨字加法与边界注入
例子与边界
先看一个确实跨字的加法
取 P = abbb 、w = 3 ,初始 P v = [ 111 , 001 ] 、M v = [ 000 , 000 ] 、S = 4 。读 a 时 E = [ 001 , 000 ] ,字数组按低字在前排列。
低字加法是 001 + 111 = 1000 :保存低三位 000,向高字传一。高字于是算 000 + 001 + 1 = 010 。异或旧 P v 并或上 E ,清掉第四行以外的填充位后得到 X h = [ 111 , 001 ] 。末行的负水平差分为一,分数从 4 降到 3 ,对应保留 a、删除三个 b。
若在每个字都把输入进位重置为零,高字会算成 000 + 001 = 001 ,其 X h 有效位变为零,错误地返回 4 。这不是“大例子才有”的整数溢出问题,而是在第四个模式位置就能复现的依赖丢失。
同一文本,两种边界
令 P = ababa ,流入文本 zzababa,仍用 w = 3 。两个字的有效掩码是 [111,011];末行对应高字的第二位 ,不是三位字的最高位。
文本边界 j
刚读字符
global 分数
infix 分数
0
空
5
5
1
z
5
5
2
z
5
5
3
a
4
4
4
b
3
3
5
a
2
2
6
b
3
1
7
a
2
0
global 必须为前面的两个 z 付费,最终距离为二;infix 可以从边界二开始,最终后缀 ababa 完全匹配。若允许至多一次编辑,infix 的合格端点是 6 , 7 ,global 没有合格端点。
在 infix 的第 3 步,初始正差分仍全为一,E = [ 101 , 010 ] 。低字算 101 + 111 = 1100 ,把进位传到高字,最终 X h = [ 111 , 011 ] ;P h = [ 000 , 000 ] 、M h = [ 111 , 011 ] 。分数减一后,把 M h 左移得到 [110,011],把 P h 左移并注入零仍是 [000,000]。新 P v = [ 110 , 011 ] , M v = 0 ,重建列为
C [ 0. .5 , 3 ] = [ 0 , 0 , 1 , 2 , 3 , 4 ] . 最终 infix 列为 [ 0 , 0 , 1 , 0 , 1 , 0 ] ,两个差分向量是 P v = [ 010 , 001 ] 、M v = [ 100 , 010 ] 。global 最终列则为 [ 7 , 6 , 5 , 4 , 3 , 2 ] ,全部有效垂直差分为负。下载结果逐列保存完整核对值。
端点不是起点,费用不是脚本
模式 ab 对文本 acb,在末边界 3 的 infix 分数为一。后缀 acb 可以插入 c 得到,后缀 cb 可以替换首字母得到;两个不同起点都达到最小值。一个分数不能决定选哪一条解释。本页也没有保存历史列的前驱,不能把这些输出称作已重建的编辑脚本。
m = 0 时 global 返回 j ,infix 返回零,直接按边界处理,不读取不存在的最高位。任意 m 下,初始 j = 0 分数都是 m 。阈值 k ≥ m 时 infix 每个边界都合格,因为空后缀总是候选;因此“报告一个合格端点”也不保证找到了非空片段。
边界的一位之差可以用 P = a , T = ba 验证:global 最终为一,infix 为零。如果把两种模式都固定注入一,后者就悄悄变成前者。
推论与应用
时间按实际字数计,输出另算
设字母表大小为 σ ,q = ⌈ m / w ⌉ 。参考程序先建立 σ 行的掩码表,再逐位置置位,编译时间为 O ( m + σ ( q + 1 ) + 1 ) 。每个非空模式的扫描步骤包含常数趟长度 q 的布尔、加法和移位循环,因此是 O ( q ) 个字操作。扫描全文并保存每个新边界的分数,统一耗时
O ( m + σ ( q + 1 ) + n ( q + 1 ) + 1 ) . 掩码表、当前差分、临时数组与单步诊断记录共占 O ( σ ( q + 1 ) + q + 1 ) 个字;feed 返回的 n 项分数另占 O ( n ) 。step 不保存旧文本或全部历史。column() 是额外的 O ( m + 1 ) 时间与空间诊断操作,不能在每字节都调用它后仍声称扫描仅用 O ( n q ) 。
实现把向量拆成宽度1至63的字,加法中间数至多多一位;小宽度用于测试跨字传递。位置和分数不超过63位非负整数,模式最多4096字节。参数、字节、块类型和位置上限均在改动状态前检查。理论模型要求地址与计数器可放入机器字,不能把调试用的一位逻辑块宽当成整台机器的地址宽度。
它利用的不是小编辑数
本算法的扫描成本不乘阈值 k ,也不依赖实际最小费用很小:无论最终分数是多少,每个符号都推进整列差分。它与Myers 1986 最短插删前沿 理路 Myers 最短插删脚本算法 Myers O(ND) algorithm · Shortest edit script 按插删次数扩展编辑图的最远对角前沿,在差异较小时避免填满网格,并记录蛇形匹配段重建脚本。 同属一个作者的工作,却是不同算法。1986方案按编辑次数扩展最远对角位置,允许插入和删除;本页1999方案打包相邻差分,包含费用一的替换。例如 a 到 b 在本页费用一,在仅插删模型中费用二。
改变费用或加入转置,需要重新推导差分范围与格子逻辑。删除费用二时,初始垂直差分已经是二,两个仅编码 − 1 , 0 , 1 的向量无法原样表示。把阈值检查换一个数不能修复这种状态不充分。带状算法、期望时间剪枝和脚本重建也不包含在这份全列接口里。
参考程序 另用普通标量网格逐列比较全部数值,覆盖两种边界、空模式、跨字长度与分块输入;结果文件 保留上述轨迹和被击穿的快捷写法。终点任务 要求从位向量重建一列,再用独立网格验证分数,而不只接受一个最终整数。
参考资料
Gene Myers, “A Fast Bit-Vector Algorithm for Approximate String Matching Based on Dynamic Programming”, Journal of the ACM 46(3), 1999, pp.395–415:论文记录 ,原文副本 。§§2–3给出自由文本前缀边界、三值差分、布尔格子转移、加法传播及基本算法;§3中的边界说明支持本页两种第零行设置。这里实现§3.7的多字向量模拟,不借用后续带状扩展的期望时间结论。