Kmp匹配

\(\text{KMP}\)匹配

\(kmp\) 匹配是一种高效的求两个字符串,其中一个串是否为另一个串的子串,并且要算出匹配位置的算法。

思想:

我们可以这么想,假如说现在有两个字符串

a b a a b b a b a b a b

a b a b

如果一位一位去匹配,时间是 \(O(nm)\) ,会非常慢,因此就诞生了 \(kmp\) 算法。

\(kmp\) 算法的核心是:

求被匹配的一个字符串内最大前缀和后缀相等长度

怎么理解呢?举个例子:

a b a a b b a b a

这个字符串前面有 a b a,后面也有 a b a ,因此他们最大匹配长度是 \(3\)

先上代码:

for(int i=2;i<=m;i++){
    int j=F[i-1];//初始最大前缀和后缀相等长度
    while((B[j+1]!=B[i])&&(j>=1))
        j=F[j];//判断此时前缀后缀同时往后也有相同的字符,如果没有
                //就要把j赋值成这个前缀最后一个字符在字串b中的位置
    if(B[j+1]==B[i]) j++;//判断是否有相等前缀后缀,因为前面已经算完了,j就是i之前最长前缀后缀相等
    F[i]=j;
}

我们用 \(F\) 数组记录前缀后缀相等的最长位数,比较难理解,可以体会一下。

两字符串匹配

看了上面,我们虽然求出了匹配长度,但是距离答案还很远,我们仍要去两个字符串去进行计算。

由于我们已经算出来如果不匹配时每一步要跳多少个数了,那么直接判断就行。

上代码:

for(int i=1;i<=n;i++){
    while(j>0&&A[i]!=B[j+1]) j=F[j];//跳的位数
    if(A[i]==B[j+1]) j++;
    if(j==m) cout<<i-m+1<<endl,j=F[j];
}

特殊性质:

定义 \(n=strlen(s)\) , \(F\)\(KMP\) 中的数组

如果 \(n\%(n-F[n])=0\) ,那么字符串为 循环字符串,循环次数为 \(n/(n-F[n])\)

否则就不是有循环节的字符串

注意事项:

1.字符串匹配中

while((B[j+1]!=B[i])&&(j>=1))

这一步要注意比的是原来字符串,不是 \(F\) 数组,之前 CF1029A 中这一点就写错了。

2.要注意字符串的输入方式,如果输入为

scanf("%s",s+1);

那么数组下标从\([1-n]\),否则,就从 \([0,n-1]\)

实际运用:

\(KMP\) 可以套到 \(dp\) 中,进行计算:

题目:一个字符串 \(A\) 中有多少个相同的另一个字符串 \(B\),注意不能重叠。

\(dp[i]\) 表示以第 \(i\) 位结尾时,\(B\)\(A\) 中的个数,则有转移方程:

//kmp 的匹配过程
dp[i]=dp[i-1];//首先继承
if(j==len(B)) dp[i]=max(dp[i],dp[i-m]+1);

最后输出 \(dp[len(A)]\) 就是答案。

posted @ 2021-03-16 18:51  Evitagen  阅读(69)  评论(0)    收藏  举报