Knuth-Morris-Pratt算法中的冗余指令

时间:2015-01-27 21:56:50

标签: algorithm optimization knuth-morris-pratt

Knuth-Morris-Pratt algorithm旨在找到字符串中第一个(可能是下一个)子字符串的出现。由于 substring 可以包含重复部分,因此它使用某种回溯机制。这是伪代码中的算法:

let m ← 0, i ← 0
while m + i < length(S) do
        if W[i] = S[m + i] then
            if i = length(W) - 1 then
                return m
            let i ← i + 1
        else
            if T[i] > -1 then
                let m ← m + i - T[i], i ← T[i]
            else
                let i ← 0, m ← m + 1

(来自维基百科)。使用W子字符串和S要搜索的字符串,这两个都是从零开始的数组。

我对算法中的最后一个if子句有疑问:if T[i] > -1 then,基于T - 矢量构造算法,似乎只有T[i]可能是i = 0索引i ← 0小于零。在这种情况下,人们可以执行更快的检查&#34;在索引上(数组访问是一个额外的指令,特别是如果考虑到 cache-faults ),就像赋值T一样。

let pos ← 2, cmd ← 0 let T[0] ← -1, T[1] ← 0 while pos < length(W) do if W[pos-1] = W[cnd] then let cnd ← cnd + 1, T[pos] ← cnd, pos ← pos + 1 else if cnd > 0 then // (*) let cnd ← T[cnd] else let T[pos] ← 0, pos ← pos + 1 的构造由以下算法完成:

0

(来自维基百科)。

现在可以看到算法只将cndT的值写入cmd。对于第一种类型的赋值,该陈述很简单。对于第二种情况,它取决于cmd的值。

现在可以减少(*)的唯一方法是第二种情况cmd,在这种情况下,cmd将变得越来越小,直到其值为零或更小。但是,由于0从数组的已初始化部分获取值,因此可以是-1cmd。如果-1确实是T[pos],则会导致0设置为cmd,因为在设置值之前会有一个增量。如果let m ← 0, i ← 0 while m + i < length(S) do if W[i] = S[m + i] then if i = length(W) - 1 then return m let i ← i + 1 else if i > 0 then let m ← m + i - T[i], i ← T[i] else let m ← m + 1 为零,则完全没有问题。

因此会有更高效的算法:

-1

这句话是否正确?如果没有,你能给出一个子字符串,其中T - 数组中出现两个或更多{{1}}个字符串吗?

1 个答案:

答案 0 :(得分:2)

这对我来说很好,虽然我不知道它在实践中会有多大的不同。毫无疑问,在常见情况下,大多数循环恰好是i为0且位置S[m]W[0]的字符。

我不认为维基百科中的算法是“官方的”或过度优化的;它的目的是说教。

if的第二个分支出现在遇到一个不能扩展任何候选匹配的字符时,并且不是被搜索的单词的第一个字符;在这种情况下,有必要移动该角色。 (这是早先提到的常见情况。)

在输入失败分支的所有其他情况下,m+i不会改变。在成功案例和最终失败案例中,m+i仅增加一个。

由于minmax是许多CPU上的无分支操作码,因此另一种优化方法是将T[0]设置为0而不是-1,并且将循环更改为:

let m ← 0, i ← 0
while m + i < length(S) do
    if W[i] = S[m + i] then
        if i = length(W) - 1 then
            return m
        let i ← i + 1
    else
        let m ← m + max(1, i - T[i]), i ← T[i]

但更好的优化是使用三个不同的循环:一个用于常见情况(i = 0S[m]不匹配W[0]);一个用于字符匹配的情况;一个是失败案例。 (失败案例不需要将m + i与输入长度进行比较;它只需要检查i是否为0。)

作为参考,原始论文(可在citeseer上获得)提供以下简单算法:

(* Note: here, m is the length of pattern and n is the length of the input *)
j := k := 1;
while j ≤ m and k ≤ n do
    begin
        while j > 0 and text[k] ≠ pattern[j]
            do j := next[j];
        k := k + l; j := j + l;
    end;

然而,作者抱怨上述简单算法效率不必要,并且花了几页来探索优化。

请参阅Fast Matching in Strings, 1974, Knuth, Morris & Pratt