版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhaohaibo_/article/details/83189759
写在开头:KMP真的好难理解。。
KMP核心思想:利用已经部分匹配这个有效信息,保持i指针不回溯,通过修改j指针,让模式串尽量地移动到有效的位置。
背景:如下图,模式串在j处失配,下一次模式串向后移3个单位(j=3)可重新匹配,直接移3个会比普通的回溯(移1个单位)更有效。
如何确定失配时,下一次应该匹配的位置(模式串后移的单位)?
- next数组:指导模式串在j位置失配时,下一次应该匹配的模式串的位置为next[j]。上例中,j=6时失配,保持文本串不动(i不动),令模式串动移动到j=3,即next[6] = 3
- :next[6]=3: 除了可以理解为后移3个单位,也可以理解为: 以i=6结尾,最多与A的前缀匹配到3.
如何确定next[j] ?
- 在模式串P[1,j)中,最大子匹配的 记模式串的真前缀和真后缀的长度为k,则next[j] = k+1(新的j位置前面k个位置要匹配,第k个位置是要与主串的i位置判断的)。上例中,模式串为ABCABB,j=6时失配,其真前缀为A,AB,ABC,ACBA,ABCAB;其真后缀为B,AB,CAB,BCAB,ABCAB,最大的匹配长度为2,即AB匹配AB,则next[6] = 2+1 = 3。
实战一下求解next数组:
模式串 = ababaaab
对模式串求next数组
num | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
P | a | b | a | b | a | a | a | d |
next | 0 | 1 | 1 | 2 | 3 | 4 | 2 | 2 |
然而,我们不可能通过一个一个数前缀后缀的方式确定next数组的每一个元素。
我们通过下列三个规则计算KMP算法中的next数组:
- 初始化next[1] = j = 0,假设next[1 ~ i-1]已求出,下面求解next[i]。
- 不断尝试扩展匹配长度j,如果扩展失败(下一个字符不想等),令 j 变为 next[ j ],直至 j 为0(应该重新从头开始匹配)。
- 如果能够扩展成功,匹配长度 j 就+1,next[ i ] 的值就是 j 。
// 计算模式串next数组
void calc_next() {
Next[1] = 0;
for (int i=2,j=0;i<=lb;i++){
while(j && b[i]!=b[j+1])
j=Next[j];
if(b[j+1]==b[i])j++;
Next[i]=j;
}
}
// 寻找所有子串在文本串中出现的起始下标
void find_pattern(){
for(int i=1, j=0;i<=la;i++){
while(j>0 && b[j+1]!=a[i])
j=next[j];
if (b[j+1]==a[i])
j++;
if (j==lb){
cout<<i-lb+1<<endl;
j=next[j];
}
}
}