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)]\) 就是答案。