KMP算法的思想是:在模式串和主串匹配过程中,当遇到不匹配的字符时,对于主串和模式串中已对比过相同的前缀字符串,找到长度最长的相等前缀串,从而将模式串一次性滑动多位,并省略一些比较过程。(遇到两个字符不匹配的情况时,希望可以多跳几个字符,减少比较次数);
KMP主要分两步:求next数组、匹配字符串。个人觉得匹配操作容易懂一些,疑惑我一整天的是求next数组的思想。所以先把匹配字符串讲一下。
一.匹配字符串
s串 和 p串都是从1开始的。i 从1开始,j 从0开始,每次s[ i ] 和p[ j + 1 ]比较
当匹配过程到上图所示时,
s[ a , b ] = p[ 1, j ] && s[ i ] != p[ j + 1 ] 此时要移动p串(不是移动1格,而是直接移动到下次能匹配的位置)
其中1串为[ 1, next[ j ] ],3串为[ j - next[ j ] + 1 , j ]。由匹配可知 1串等于3串,3串等于2串。所以直接移动p串使1到3的位置即可。这个操作可由j = next[ j ]直接完成。 如此往复下去,当 j == m时匹配成功。
代码:(前提输入n,输入字符串p(长度为n),输入m,输入字符串s(长度为m)//字符串p短于s
n<m;
for(int i = 1, j = 0; i <= m; i++)
{
while(j && s[i] != p[j+1]) j = ne[j];
//如果j有对应p串的元素, 且s[i] != p[j+1], 则失配, 移动p串
//用while是由于移动后可能仍然失配,所以要继续移动直到匹配或整个p串移到后面(j = 0)
if(s[i] == p[j+1]) j++;
//当前元素匹配,j移向p串下一位
if(j == n)
{
//匹配成功,进行相关操作
j = ne[j]; //继续匹配下一个子串
cout<<i-n;
}
}
求next数组的思想:
next数组的理解:对next[ j ] ,是p[ 1, j ]串中前缀和后缀相同的最大长度(部分匹配值)
即从p字符串第一个字母到第j个字母前缀(从第一个字母开始)和后缀(从最后一个字母开始)相同的长度;
如;
对 p = “abcab”
p | a | b | c | a | b |
下标 | 1 | 2 | 3 | 4 | 5 |
next[ ] | 0 | 0 | 0 | 1 | 2 |
对next[ 1 ] :前缀 = 空集—————后缀 = 空集—————next[ 1 ] = 0;
对next[ 2 ] :前缀 = { a }—————后缀 = { b }—————next[ 2 ] = 0;
对next[ 3 ] :前缀 = { a , ab }—————后缀 = { c , bc}—————next[ 3 ] = 0;
对next[ 4 ] :前缀 = { a , ab , abc }—————后缀 = { a . ca , bca }—————next[ 4 ] = 1;
对next[ 5 ] :前缀 = { a , ab , abc , abca }———后缀 = { b , ab , cab , bcab}————next[ 5 ] =2
代码
for(int i=2,j=0;i<=n;i++){
while(j&&p[i]!=p[j+1])j=ne[j];
if(p[i]==p[j+1])j++;
ne[i]=j;
}
这里插入一个流程图
关于KMP算法就这些,相比于暴力,KMP会大大节省时间