KMP算法是解决高效解决子串匹配查找问题的方法。
一般我们使用暴力的算法可以解决子串的匹配查找问题如下图的两种方法
上述两种方法虽然可以达到目的,但是效率不高,这是什么原因呢?
在每次依次匹配查找的时候,很多前缀重复,根本不需要重头开始,那么有什么方法可以解决呢?这时候就引入了KMP算法去解决这个问题
模式串:abcab
文本串:abcacababcab
首先,前四位按位匹配成功,遇到第五位不同,而这时,我们选择将abcababcab向右移三位,或者可以直接理解为移动到模式串中与失配字符相同的那一位。可以简单地理解为,我们将两个已经遍历过的模式串字符重合,导致我们可以不用一位一位地移动,而是根据相同的字符来实现快速移动。
模式串: abcab
文本串:abcacababcab
但有时不光只会有单个字符重复:
模式串:abcabc
文本串:abcabdababcabc
当我们发现在第六位失配时,我们可以将模式串的第一二位移动到第四五位,因为它们相同 .
模式串: abcabc
文本串:abcabdababcabc
KMPKMP 的重头戏就在于用失配数组来确定当某一位失配时,我们可以将前一位跳跃到之前匹配过的某一位。而此处有几个先决条件需要理解:
1、我们的失配数组应当建立在模式串意义下,而不是文本串意义下。因为显然模式串要更加灵活,在失配后换位时,更灵活简便地处理。
2、如何确定位置呢?
首先我们要明白,基于先决条件11而言,我们在预处理时应当考虑当模式串的第 ii 位失配时,应当跳转到哪里.因为在文本串中,之前匹配过的所有字符已经没有用了——都是匹配完成或者已经失配的,所以我们的 kmpkmp 数组(即是用于确定失配后变化位置的数组,下同)应当记录的是:
在模式串 str1str1 中,对于每一位 str1(i)str1(i) ,它的 kmpkmp 数组应当是记录一个位置 jj, j \leq ij≤i 并且满足 str1(i)=str1(j)str1(i)=str1(j) 并且在 j!=1j!=1 时理应满足 str1(1)str1(1)至str1(j-1)str1(j−1) 分别与 str(i-j+1)str(i−j+1)~str1(i-1)str1(i−1) 按位相等
上述即为移位法则
3、从前缀后缀来解释 KMPKMP :
首先解释前后缀(因为太简单就不解释了 ):
给定串:ABCABA
前缀:A,AB,ABC,ABCA,ABCAB,ABCABA
后缀:A,BA,ABA,CABA,BCABA,ABCABA
其实刚才的移位法则就是对于模式串的每个前缀而言,用 kmpkmp 数组记录到它为止的模式串前缀的真前缀和真后缀最大相同的位置(注意,这个地方没有写错,是真的有嵌套 )。然而这个地方我们要考虑“模式串前缀的前缀和后缀最大相同的位置”原因在于,我们需要用到 kmpkmp 数组换位时,当且仅当未完全匹配。所以我们的操作只是针对模式串的前缀--−−毕竟是失配函数,失配之后只有可能是某个部分前缀需要“快速移动”。所以这就可以解释 KMPKMP 中前后缀应用的一个特点:
KMPKMP 中前后缀不包括模式串本身,即只考虑真前缀和真后缀,因为模式串本身需要整体考虑,当且仅当匹配完整个串之后;而匹配完整个串不就完成匹配了吗
3、从前缀后缀来解释 KMPKMP :
首先解释前后缀:
给定串:ABCABA
前缀:A,AB,ABC,ABCA,ABCAB,ABCABA
后缀:A,BA,ABA,CABA,BCABA,ABCABA
其实刚才的移位法则就是对于模式串的每个前缀而言,用 kmpkmp 数组记录到它为止的模式串前缀的真前缀和真后缀最大相同的位置(注意,这个地方没有写错,是真的有嵌套 )。然而这个地方我们要考虑“模式串前缀的前缀和后缀最大相同的位置”原因在于,我们需要用到 kmpkmp 数组换位时,当且仅当未完全匹配。所以我们的操作只是针对模式串的前缀--−−毕竟是失配函数,失配之后只有可能是某个部分前缀需要“快速移动”。所以这就可以解释 KMPKMP 中前后缀应用的一个特点:
KMPKMP 中前后缀不包括模式串本身,即只考虑真前缀和真后缀,因为模式串本身需要整体考虑,当且仅当匹配完整个串之后;而匹配完整个串不就完成匹配了吗
模板代码
#include<iostream>
#include<cstring>
#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);
j=0;
for (int i=2;i<=lb;i++) // 设置next数组
{
while(j&&b[i]!=b[j+1]) // 如果j=0,如果是第一个字符就不跳了
j=kmp[j];
if(b[j+1]==b[i])j++;
kmp[i]=j; // 确定i+1失配查询的位置
}
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];} // 输出b在a中出现的位置
}
for (int i=1;i<=lb;i++)
cout<<kmp[i]<<" "; // 输出next的值
return 0;
}
时间复杂度Θ(m+n)