KMP算法
例如:A=abacabab B=abab找到B在A中第一次出现的起始位置
KMP算法主要是分析了匹配串B的特征,从而在A中搜索时避免用传统暴力法,一一匹配。
如果用传统匹配法,A[3]!=B[3],则应从A[1]开始再次一一比较
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| a | b | a | c | a | b | a | b |
1 | a | b | a | b |
|
|
|
|
2 |
| a |
|
|
|
|
|
|
3 |
|
| a | b |
|
|
|
|
4 |
|
|
| a |
|
|
|
|
5 |
|
|
|
| a | b | a | b |
如果用KMP算法,则需要先计算B的next数组,next[i]表示由B[0]到B[i]组成的串的部分匹配值(即串的前缀和后缀的最长共有元素的长度。“前缀”:除了最后一个字符以外,一个字符串的全部头部组合;“后缀”:除了第一个字符以外,一个字符串的全部尾部组合)
| 0 | 1 | 2 | 3 |
1 | a | b | a | b |
next | 0 | 0 | 1 | 2 |
经分析可知next[i]是所匹配前缀的下一个字符的下标
每次匹配所需移动的次数即是:匹配的字符数-next[i]
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| a | b | a | c | a | b | a | b |
1 | a | b | a | b |
|
|
|
|
2 |
|
| a | b |
|
|
|
|
那么在第二次匹配的时候就可以向后移动两步,而不用再一一比较,优化运算。尤其是在B串较长,重复串出现较多的情况下能很好的提高运算效率。
那么重点在于如何求next数组呢?
void makeNext(string P,int len,vector<int> &next)
{
int q,k; //q:模版字符串下标;k:最大前后缀长度
next[0] = 0;//模版字符串的第一个字符的最大前后缀长度为0
for (q = 1,k = 0; q < len; ++q)//for循环,从第二个字符开始,依次计算每一个字符对应的next值
{
while(k > 0 && P[q] != P[k])//递归的求出P[0]···P[q]的最大的相同的前后缀长度k
//********************************************************************
k = next[k-1]; //精髓,重点理解
//********************************************************************
if (P[q] == P[k])//如果相等,那么最大相同前后缀长度加1
{
k++;
}
next[q] = k;
}
}
程序中k=next[k-1]这一步比较难理解,前面已经说过next[i]是所匹配前缀的下一个字符的下标,而next[k-1]则表示B[0]到B[k-1]组成的串的所匹配前缀的下一个字符的下标
0 | 1 | ... | q-k | ... | q-1 | q | ... |
|
|
|
|
| 0 | ... | k-1 | k | ... | q | ... |
|
|
|
|
|
|
|
|
|
|
假设B[0]到B[q-1]组成的串的前缀是B[0]到B[k-1],而B[0]到B[k-1]组成的串的前缀是B[0]到B[j-1],那么next[k-1]=j,如果B[j]与B[q]相等,那么B[0]到B[q]组成的串的前缀是B[0]到B[j],否则就需要继续寻找B[0]到B[j-1]中的前缀,依次类推。
0 | 1 | .... | q-k | ... | ... | q-1 | q | ... |
|
|
|
|
|
|
| 0 | ... | ... | k-1 | k | ... | q | ... |
|
|
|
|
|
| 0 | ... | j-1 | j | ... | k | ... | q | ... |
同时在真正匹配的时候以A为基准,而尝试在B中找到与A[i]相等的字符,一旦B串找到了末尾,就认为匹配成功。
int findAppearance(string A, int lena, string B, int lenb) {
// write code here
vector<int> next(lenb,0);
makeNext(B,lenb,next);
int i,q;
for(i=0,q=0;i<lena;i++)
{
while(q>0 && A[i]!=B[q]) q=next[q-1];
if(A[i]==B[q]) q++;
if(q==lenb) return i-lenb+1;
}
return -1;
}