KMP算法
主要应用:字符串匹配
比如我们从字符串"acfacfgded"(需要在哪里找的字符串称为“文本串”)找其中是否包含字符串"acfg"(需要从文本串里找的字符串我们叫做“模式串”),我们一般会想到的解法是暴力求解,两层for循环,依次对模式串的每一个元素进行匹配,如果匹配失败,下次还从模式串的第一个进行匹配,这就导致了较高的时间复杂度(O(n×m))。
而KMP算法不同之处就在于,当模式串的某个元素匹配失败后,不需要再从模式串的第一个元素从头开始匹配了,而是根据前缀表(next)找到模式串中一个最优的位置继续进行匹配
看具体题目Acwing 831.KMP字符串
给定一个字符串 S,以及一个模式串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模式串 P在字符串 S中多次作为子串出现。
求出模式串 P在字符串 S中所有出现的位置的起始下标。
输入样例:
3
aba
5
ababa
输出样例:
0 2
思路分析:
1. next数组
核心:next[i]记录的是模式串下标i(包括i)之前的最长相等前后缀的长度。这里我们规定next[1]=0,因为第一个字符就不匹配,只能从0重新开始;
对于字符“acdac”
前缀 | 后缀 | 最长相等前后缀 |
---|---|---|
a,ac,acd,acda | cdac,dac,ac,c | ac |
代码求解:思想类似KMP匹配两个字符串,特殊的只是将自己与自己进行匹配。
//求next的过程:本质为寻找字串中最长相等前后缀的长度
for(int i = 2,j = 0;i <= n;i ++){//ne[1] = 0,第一个失败,只能从0开始
while(j && p[i] != p[j+1]) j = ne[j];//退而求此次
if(p[i] == p[j+1]) j ++;//看对眼,前进一步
ne[i] = j;//p[1,j]==p[i-j+1,i]即这是最长相同前后缀的长度(j),
//前缀为为1~j段;后缀为i-j+1~i这段
}
2.KMP匹配过程
得到next数组之后,设指向文本串的指针为i,i从1开始,指向模式串的指针为j,j从0开始;文本串数组为s[],模式串数组为p[],next数组为ne[].文本串为“ababa”,模式串为“aba”
- 开始循环比较s[i]与p[j+1],当出现j>0 且s[i]!=p[j+1]时,j指针跳到ne[j],即让模式串向右平移一段位置,而不是暴力那样直接模式串后移一位重新开始比较。
过程模拟如下:
kmp匹配过程板子:
//kmp匹配过程,利用next数组
for(int i = 1,j = 0;i <= m;i ++){//i遍历s所有字母
while(j && s[i] != p[j+1]) j = ne[j];//若上下不能匹配了,则j移动到ne[j]
if(s[i] == p[j+1]) j++;//若已匹配,j后移一位
if(j == n){
//匹配成功
printf("%d ",i-n);
j = ne[j];//继续匹配下一个
}
}
具体实现代码:
#include <iostream>
using namespace std;
const int N=1e6+10;
int n,m;
char p[N],s[N];
int ne[N];
int main(){
cin >> n >> p + 1 >> m >> s + 1;//下标从1开始
//求next的过程:本质为寻找字串中最长相等前后缀的长度
for(int i = 2,j = 0;i <= n;i ++){//ne[1] = 0,第一个失败,只能从0开始
while(j && p[i] != p[j+1]) j = ne[j];//退而求此次
if(p[i] == p[j+1]) j ++;//看对眼,前进一步
ne[i] = j;//p[1,j]==p[i-j+1,i]即这是最长相同前后缀的长度(j),
//前缀为为1~j段;后缀为i-j+1~i这段
}
//kmp匹配过程,利用next数组
for(int i = 1,j = 0;i <= m;i ++){//i遍历s所有字母
while(j && s[i] != p[j+1]) j = ne[j];//若上下不能匹配了,则j移动到ne[j]
if(s[i] == p[j+1]) j++;//若已匹配,j后移一位
if(j == n){
//匹配成功
printf("%d ",i-n);
j = ne[j];//继续匹配下一个
}
}
return 0;
}
以上就是KMP算法的基本方法,关键在于next数组的计算,其代码实现相比原理来说更为简单,建议可以多些几遍,加深记忆。