KMP字符串匹配算法
常见的 $O(n*m)$ 暴力算法
for (int i=1;i<=n;i++){
int j,k;
for (j=i,k=1;k<=m;j++,k++){
if (s1[j]!=s2[k])
break;
}
if (k==m) cout<<i;
}
最外层用 $i$ 枚举每个字符串首的位置,内层用两个指针 $j,k$ 分别指向 $s1,s2$
如果 s1[j]!=s2[k]
,说明在该位置上不能匹配,则退出内层循环,将 $i$ 后移
如果内层循环出来后 k==m
,则可以匹配到相同的字符串,输出此时 $i$ 的位置
这种做法可以简单理解,但是时间复杂度非常的高
改进这种做法,发现到每次匹配的过程中我们都会舍弃掉已经匹配过的字符,实际上这些字符可以提供一定的信息
s1:aabaabaabaabaaaa
s2:aabaabaaaa
*图引自董晓算法
模拟一下过程,当 i=1,j=k=9
时不能匹配,暴力做法是将 $i$ 后移,在下一个字符开始重新遍历
但是可以发现,i=3
时可以直接匹配到 j=k=9
,这样就节省了 i=2
遍历的时间
我们可以打一张表来映射 j,k
的位置与 i
移动的位置的关系
int ne[N];
ne[1]=0;
for (int i=2,j=0;i<=m;i++){
while (s2[i]!=s2[j+1]&&j) j=ne[j];
if (s2[i]==s2[j+1]) j++;
ne[i]=j;
}
双指针对 $s_2$ 进行操作,用来计算 ne[i]
,即匹配到 i
位置不同后,指针的移动距离
在预处理过程中,$i$ 指针用来遍历 $s_2$ 串,$j$ 指针用来计算最长相同前后缀的长度
如果 $i,j+1$ 指向的字符不同,那么可以通过之前的结论类似递推的方式计算
while (s2[i]!=s2[j+1]&&j) j=ne[j];//如果j+1指向的元素与i不同,那么考虑从j位置上的最长相同前后缀转移
最终要是都不能匹配,则 $ne[i]=0$ ,表示不存在最长相同前后缀,应用到匹配过程中,即不能跳跃移动最外层的指针
预处理的时间复杂度为 $O(m)$
匹配过程中,写法也很类似
for (int i=1,j=0;i<=n;i++){
while (s1[i]!=s2[j+1]&&j) j=ne[j];
if (s1[i]==s2[j+1]) j++;
if (j==m) cout<<i-m+1<<endl;
}
同样是双指针,由于只会遍历 $s_1$ 中的每个元素最多一次,所以匹配的时间复杂度为 $O(n)$
我们这里考虑移动指向 $s_2$ 的指针 $j$ 的位置,每次利用 ne
进行回跳
*图引自董晓算法
评论区(暂无评论)