字符串匹配算法1-KMP

前面介绍过,字符串搜索一般来说有三种方式,前缀搜索,后缀搜索,子串搜索。KMP使用的是前缀搜索。

假设p的偏移是i,也就是窗口的位置是i,匹配到位置j+1时发现了不匹配。现在的问题是向前移动窗口到什么位置呢?注意要保证不漏掉可能的匹配,而且为了效率,应该尽量的多移动窗口。

先假设窗口移动到了位置k,看看有什么后果,这里i<kj+1,大于i好理解,至少移动一个字符嘛,为什么小于等于j+1?因为σ和u[0]是否相等是不知道的,为了不错过可能的匹配,窗口最多移动到j+1。现在看看子串v有什么性质。子串v应该等于t[k…j],否则k这个位置肯定没有匹配,可以继续移动窗口了。而v=t[k…j]意味着vu的一个边界。应该注意到u可能不止一个边界,为了不遗漏匹配,v应该是最长的边界。

根据这种思想,MorrisPratt提出:

  1. 对于模式串的每个前缀u,预先计算u的最长边界b(u)
  2. 设窗口位置为i,当前文本位置为ju既是p的前缀,同时也是t[1...j]的后缀的最长字符串。显然u=t[i…j],新读入的文本字符为σ=t[j+1],那么按照如下步骤计算新的最长前缀。
    1. 如果σ=p[|u|+1],那么新的最长前缀是up[|u|+1],计算结束,窗口位置不变;
    2. 如果u=ε,那么新的最长前缀是ε,计算结束,窗口移动到i+1

否则,置u←b(u),窗口移动|u|-|b(u)|,即i←i+|u|-|b(u)|,转到步骤1执行。

  1. 计算出新的u后,比较up(实际上是比较长度),如果u=p,那么发现了一个匹配。然后继续读入下一个文本字符σ=t[j+2],继续ii

Knuth提出了一个改进,事实上,如果σ≠p[|u|+1],则u的任何边界的后继字符若要与σ=t[i+1]匹配,就一定不能等于p[|u|+1]。基于这个事实,在预处理阶段,对于p的每个真前缀u(p=uw, w≠ε),可以预先计算其最长边界v,使得p[|u|+1]≠p[|v|+1](图中α≠β)。这样,如果σ≠p[|u|+1],那么窗口直接从i移动到i+|u|-|v|,然后再从p|v|+1位置开始比较(σβ比较)。

 

那要如何计算p的真前缀u的最长边界v,且p[|u|+1]≠p[|v|+1]

我们首先假设现在要处理子串u=p[0…i],找到它满足条件的v,并把它的长度放到数组kmpNext中。注意kmpNext[i]表示p[0…i-1]的满足条件的v的长度。kmpNext[0]=-1

首先找到u的最长边界。现在设p[0…i-1]的最长边界为v|v|=j。现在看α=p[i]是否和σ=p[j]相同。如果α=σ,则u的最长边界。如果α≠σ,则不是u的边界,要重新找最长边界。

那么找哪个子串呢?看下图:

假设w是这个子串,从图中可知,wv的边界,设|w|=k。其次,β=p[k]≠σ,因为如果β=σ,那么β≠α,也即是不是u的边界。从上面两点可知,wv的满足条件的边界。因此如果发现α≠σ,下次就比较β=p[k]σ。如果相同,那么就是u的最长边界,否则继续取w的满足条件的边界进行上述操作。

 

找到最长边界后(设最长边界为v),比较p[i+1]p[j+1]

如果p[i+1]≠p[j+1],则v满足条件,kmpNext[i+1]=j+1;相同的话,v不满足条件,那么kmpNext[i+1] = kmpNext[j+1],这个解释一下,由于vu的最长边界,那么满足条件的边界w也应该是v的边界(边界的性质),而且w应该是v满足条件的边界,这样p[k]p[j+1]=p[i+1],那么w也是u的满足条件的边界,即kmpNext[i+1] = kmpNext[j+1]

处理完u后,按照上面的算法继续处理up[i+1],注意到此时vp[j]u的最长边界。

我们可以注意到,预处理的过程和查询的过程很类似,只不过模式p作为文本,而p的前缀作为模式,也就是说,我们使用KMP算法来对p进行预处理。

 

下面是KMP算法的C语言实现:

 1 #define XSIZE 1024
 2 /*kmpNext中保存p的每个真前缀u的最长边界v的长度,v要满足p[|u|+1]≠p[|v|+1]*/
 3 void preKmp(char *p, int m, int kmpNext[]) 
 4 {
 5    int i, j;
 6 
 7    i = 0;
 8    j = kmpNext[0] = -1;
 9    while (i < m) {
10       while (j > -1 && p[i] != p[j])
11          j = kmpNext[j];
12       i++;
13       j++;
14       if (p[i] == p[j])
15          kmpNext[i] = kmpNext[j];
16       else
17          kmpNext[i] = j;
18    }
19 }
20 
21 
22 void KMP(char *p, int m, char *t, int n) 
23 {
24    int i, j, kmpNext[XSIZE];
25 
26    /* Preprocessing */
27    preKmp(p, m, kmpNext);
28 
29    /* Searching */
30    i = j = 0;
31    while (j < n) {
32       while (i > -1 && p[i] != t[j])
33          i = kmpNext[i];
34       i++;
35       j++;
36       if (i >= m) {
37          printf("Found match in %d\n", j - i);
38          i = kmpNext[i];
39       }
40    }
41 }

 

posted on 2013-11-14 17:28  frank van  阅读(312)  评论(0)    收藏  举报