假设文本串为S,模式串为P
此篇博客只是为了辅助理解KMP算法,更好的理解KMP算法模板,详细推导过程并未说明。
1.暴力匹配
for (int i = 0; i < n; i++)
{
bool flag = true;
for (int j = 0; j < m; j++)
{
if (s[i+j] != p[j])
{
flag=false;
break;
}
}
if(flag) {
//匹配成功
}
}
2.KMP
KMP算法就是通过某些预处理(next数组),使得每次如果Si和Pj不匹配时,不再直接回溯 j 的位置到起始位置,而是回溯到某一个中间位置(next[j+1]),继续匹配,且不再回溯 i 的位置。如下图:
()
从上图应该可以很容易的看出KMP和朴素做法的区别,且如果知道不匹配时j
应跳转的位置(next数组),可以很容易写出代码。实际上整个KMP算法代码很是很简短的
//匹配过程
for(int i = 0, j = -1; i < m; i++) { //m表示文本串长度
while(j != -1 && s[i] != p[j+1]) { //不匹配则回溯j到ne[j]位置
j = ne[j];
}
if(s[i] == p[j+1]) { //当前位置匹配,判断下一个位置
j++;
}
if(j == n-1) { //匹配到结尾,则再文本串位置找到模式串
res++; //res表示模式串在匹配串中个数,在求其他问题时,可变换
j = ne[j]; //继续寻找下一个模式串
}
}
那么,整个KMP的重点就是理解next数组了。
上图中L1, L2,L3, L4,L5表示的字符串序列是绿色框中字符串(字符串序列用线段表示了)。那么显然L1 == L2,L3 == L4。同时可以得出L5 == L4。当第 j 个元素不匹配时,变换后的前缀(P0…ne[j])和变换前的后缀相等(从第 j-1 个元素向前取相同长度字符)。
next[j]表示的是P0…j字符串的前缀与后缀相等的最长长度-1(-1是因为下标从0开始,实质上是最大长度)。
且在求解next[j]时,只和模式串P有关,而和文本串S无关。
理解了next[]数组的含义后,那么也可以发现求next[]数组本身也是一种字符串匹配得过程。
int ne[MAXN]; //next数组,ne[j]表示P~0...j~字符串的前缀与后缀相等的最长长度-1(-1是因为下标从0开始,实质上是最大长度)
void init(int n, string p) { //n表示模板串长度,p表示模板串
ne[0] = -1; //-1表示长度为0
for(int i = 1, j = -1; i < n; i++) { //i指向文本串,j指向模式串
while(j != -1 && p[i] != p[j+1]) {
j = ne[j];
}
if(p[i] == p[j+1]) {
j++;
}
ne[i] = j;
}
}