让一个字符串H(对于Haystack),我们希望在其中递归地擦除字符串N的每一次出现(对于针)。我将描述的过程执行模式的从左到右删除。
主要思想
要做的第一件事是在H中找到每个不重叠的N。您可以使用 Z-algorithm 在 O(|N| + |H|) 中执行此操作。让 S1,..., Sk 这些出现。
最初,每个 Si 都有一个起始位置(它在原始字符串中的索引)和一个结束位置。我们将跟踪这些职位。让 b(i) 和 e(i) 成为这些位置。
删除 Si 可能会产生另一个针线匹配。只有在之前有一些字符时才可以这样做。这里,我们只对窗口(1)感兴趣:
W(i) = L(i) U R(i),其中:
L(i ) = [e(i) - |N| + 1, e(i)-1]
R(i) = [s(i)+1, s(i) + |N| - 1]
使用 Z 算法的计算值,我们可以探索窗口的“左侧”部分。
Z 属性: Z 值 在位置 e(i) - k 必须是至少 k em> 匹配存在。
因此,如果左侧窗口中没有满足此标准的索引,我们就完成了 Si。否则,我们为 W(i) 构建一个新的 Z-array。 W(i) 中的 N 只能有一个非重叠匹配。我们将永远选择“最左边”。
这场比赛将有一个新的开始位置b(i)(在L(i)中)和一个更新的结束位置e(i) em> 在 R(i) 中。类似地,我们更新 L(i) 和 R(i) 以使用 (1) 中的等式与新的 b(i) 和 e(i)。递归一直持续到 L(i) 中没有字符或 L(i) 中没有字符满足 Z 属性。
这个想法是使用上面显示的递归方法处理每个 Si。我们将从最左匹配(i = 0)到最右处理它们。
请注意,给定匹配 i 的左侧窗口可能会与其他匹配的某些已删除字符重叠
处理完每个 Si 后,您将拥有一组定义已删除字符的窗口。您以这种方式处理 W(i):
- 令 s = 0, e = |H| - 1
- 对于 i=0..nmatch - 1:
- e = L(i)[|L(i)|-1]
- 复制H[s..e]到O
-
s = R(i)[0], e = |H| - 1
- 复制H[s..e]
经过此过程,O 就是您要查找的输出字符串!
解决“擦除索引”问题
在处理任何 i > 1 的 Si 时,定义窗口可能会出现问题。实际上,e(i) - k 可能落在给定 k 的“已擦除”索引上。
要解决这个问题,您可以在窗口和原始字符串中的 实际 位置之间构建一个映射数组。要计算这些值,您可能需要遍历 Sj、j 的窗口。
最初,设置 L(i)[k] = e(i) - |N| + 1 + k 对于 k = 0..|N|-2。
对于给定的索引 m 使得 L(i)[m] = t = e(i) - k:
而 (i > 0 和 t )
- 如果 δ = t - R(i-1)[0] :
- 如果 |δ| > |L(i-1)|:
- 令 δ = δ - |L(i-1)| 和 t = L(i-1)[0] - δ。
- i = i-1,继续下一个循环迭代
- 否则,让 L(i)[m] = L(i-1)[|L(i-1) + δ] 并退出
- 其他退出
如果 t 为负数,则该字符不存在。此外,我们现在 abs(t) - 1 下一个字符也不存在,因此可以继续 m + abs(t ) 而不是 m + 1。最后,我们只保留窗口中已有的字符。
同样,如果δ ,那么我们知道下一个|δ| - 2 个字符的索引范围为 L(i)[|L(i)| + δ + 1] 到 L(i)[|L(i)| -1].
复杂性分析
Z 算法的初始运行时间为O(|H| + |N|)。每个匹配的处理包括窗口的维护和对后续模式匹配的实际搜索。
搜索是O(|N|),因为窗口定义了最多个2.|N|-2个字符的字符串.
windows 的维护有点难以分析。但如果我们使用我之前的评论跳过不必要的计算,最后应该最多 nmatch 。|N|。
最后,您的上限为 O(|H| + nmatch².|N|)。