1.KPM算法
该算法主要用于快速求解在主串中子串的数量,和位置,如果用暴力的话,时间复杂度是O(m*n),如果m,n的大小为int的最大值呢,那计数花费的时长就挺多的了,所以有了KMP算法,他的时间复杂度为O(m+n),时间花费大大降低。
接下来讲讲算法的实现:该算法需要用到前缀的思想,这样才能达到线性的时间,前缀主要用于存储子串的最大相同字符。这需要建立一个next数组,也可以叫KMP数组,用来表示可以跳过多少个字符。
例如:cabcabca
next为0,0,0,1,2,3,4,5 重叠的也算,第8个的“cabca”。
例如:子串:aabcaabf
下标从1开始,next数组的大小为0 1 0 0 1 2 3 0
这是为什么呢,这需要把它看成7(子串长度)个字符串来看,第i个字符串有该字符串的前i个字符。第一个只有‘a’,这个'a'既是前缀又是后缀,所以对应数组大小为0。第二个字符串有‘aa',前面一个'a',后面一个’a‘,那next[2]=2了,接下来来看第六给字符串’aabcaab‘,他们的next[6]则是3,因为前后都有’aab'。那可能有人有疑问了,那‘acbcb’,这样相同的在中间的怎么办?注意是前缀,所以只能要前面的,所以他们是0,0,0,1,1
大致就是这样
子串数组处理:
j=0;
for (int i=2;i<=lb;i++)
{
while(j&&b[i]!=b[j+1])
//此处判断j是否为0的原因在于,如果回跳到第一个字符就不 用再回跳了
j=kmp[j];
//通过自己匹配自己来得出每一个点的kmp值
if(b[j+1]==b[i])j++;
kmp[i]=j;
//i+1失配后应该如何跳
开始匹配:
int j;
j=0;//j可以看做表示当前已经匹配完的模式串的最后一位的位置
//如果楼上看不懂,你也可以理解为j表示模式串匹配到第几位了
for(int i=1;i<=la;i++)
{
while(j&&b[j+1]!=a[i])j=kmp[j];
//如果失配 ,那么就不断向回跳,直到可以继续匹配
if (b[j+1]==a[i]) j++;
//如果匹配成功,那么对应的模式串位置++
if (j==lb)
{
cout<<i-lb+1<<endl;
j=kmp[j];
//继续匹配
}
}
2.实战演练
1.题目:【模板】KMP
输入:
ABABABC
ABA
输出:
1
3
0 0 1
1.处理kmp 数组
j=0;
for (int i=2;i<=lb;i++)
{
while(j&&b[i]!=b[j+1])
//此处判断j是否为0的原因在于,如果回跳到第一个字符就不 用再回跳了
j=kmp[j];
//通过自己匹配自己来得出每一个点的kmp值
if(b[j+1]==b[i])j++;
kmp[i]=j;
//i+1失配后应该如何跳
}
2.和文本串比对
int j;
j=0;//j可以看做表示当前已经匹配完的模式串的最后一位的位置
//如果楼上看不懂,你也可以理解为j表示模式串匹配到第几位了
for(int i=1;i<=la;i++)
{
while(j&&b[j+1]!=a[i])j=kmp[j];
//如果失配 ,那么就不断向回跳,直到可以继续匹配
if (b[j+1]==a[i]) j++;
//如果匹配成功,那么对应的模式串位置++
if (j==lb)
{
cout<<i-lb+1<<endl;
j=kmp[j];
//继续匹配
}
}
完整代码:
#include<bits/stdc++.h>
#define MAXN 1000010
using namespace std;
int kmp[MAXN];
int la,lb,j;
char a[MAXN],b[MAXN];
int main() {
cin>>a+1;
cin>>b+1;
la=strlen(a+1);
lb=strlen(b+1);
for (int i=2; i<=lb; i++) {
while(j&&b[i]!=b[j+1])
j=kmp[j];
if(b[j+1]==b[i])j++;
kmp[i]=j;
}
j=0;
for(int i=1; i<=la; i++) {
while(j>0&&b[j+1]!=a[i])
j=kmp[j];
if (b[j+1]==a[i])
j++;
if (j==lb) {
cout<<i-lb+1<<endl;
j=kmp[j];
}
}
for (int i=1; i<=lb; i++)
cout<<kmp[i]<<" ";
return 0;
}
2.题目:[BOI2009] Radio Transmission 无线传输
输入:
8
cabcabca
输出:
3
为了防止特殊情况,以“cabcdcabcdc”长度为11的串例子,next对应为0,0,0,1,0,1,2,3,4,5,6有没有方向,在某些时候,他是一直增加,并且没有负数,很明显,该部分是子串分裂组成的,那个next数组中数值为1到6的连续增加的下标就是输入字符串的最大分裂长度,那用总长度减一下,就是最短子长度了。
#include<bits/stdc++.h>
#define MAXN 1000010
using namespace std;
int kmp[MAXN];
int la,lb,j;
char b[MAXN];
int main() {
int n,k;
cin>>n;
cin>>b+1;
for (int i=2; i<=n; i++) {
while(j&&b[i]!=b[j+1])
//此处判断j是否为0的原因在于,如果回跳到第一个字符就不 用再回跳了
j=kmp[j];
//通过自己匹配自己来得出每一个点的kmp值
if(b[j+1]==b[i])j++;
kmp[i]=j;
}
cout<<n-kmp[n];
return 0;
}
3.题目: