算法简介
这似乎是三个人提出来的一种算法,然后把三个人的名字合并在一起形成了KMP这个名字(真是诡异)。这个算法的本质是用来快速地匹配两个字符串。
暴力(n^2)
现在我们有两个字符串
s=abcabcabd
t= abcabd
求出字符串t是否是字符串s的一个子串(连续)。
我们显然可以分别枚举s匹配的起始位置,然后直接判断是否可以匹配即可。
时间复杂度
O(|s|∗|t|)
O
(
|
s
|
∗
|
t
|
)
KMP大法
我们可以发现在失配的时候,我们浪费了很多的时间来重新枚举,而KMP则是把这些浪费的时间全都去掉。
还是之前的两个字符串,我们匹配到第6个字符的时候发现失配了。
abcabcabd
abcabd
t串这个时候就要后移了。我们就可以用贪心的思想:t串肯定是移得越少越好嘛。
所以,我们可以定义一个next数组,next[i]表示的是:当前位置如果和第i个位置匹配,则当我们把next[i]移到到这个位置时,当前位置及前面依然是匹配的,而next[i]是整个合法方案中小于i的最大值。
所以当我们在当前位置与第i个位置失配的时候,我们就可以后移t使得next[i-1]移到当前位置的前面,接着再判断是否可以匹配,如果不行再继续,直到当前i-1没有next为止。
这样既保证了我们不会选择无用的东西,也保证了我们不会漏选有用的东西。
依然是这两个字符串,发现next[5]是2,于是我们后移
.abcabcabd
……abcabd (可能有点丑)
我们发现可以匹配了,我们就继续匹配到末尾为止。
代码(第一部分)
j=0;
for(int i=1;i<=n;i++)
{
while(j>0&&b[j+1]!=a[i]) j=p[j];
if(b[j+1]==a[i]) j=j+1;
if(j==m)
{
j=p[j];
}
}
此处代码是匹配多次,其中j=p[j]是每一次匹配成功后为了能够继续匹配需要将t串后移。
其中的p代表的就是next
next求法
现在的关键就是,我们怎么求next?
接着我们就可以神奇地发现,求next其实就是字符串与自己本身匹配(感受一下)。
然后,你是不是已经知道怎么做了呢?
代码(第二部分)
p[0]=p[1]=0;
int j=0;
for(int i=2;i<=m;i++)
{
while(j>0&&b[j+1]!=b[i]) j=p[j];
if(b[j+1]==b[i]) j=j+1;
p[i]=j;
}
时间复杂度证明
这个算法是个二重循环,总觉得怎么都不像
O(|s|+|t|)
O
(
|
s
|
+
|
t
|
)
。
我们来证明一下:
对于j(代码中的),每一次进入while,它都会至少减1,当它=0时,就会退出
而我们知道,j最多只会加|s|或|t|次1,因为成功匹配次数不会大于字符串长度。
所以,进入while循环的次数最多只会是|s|或|t|次。
时间复杂度就是线性的。
模板题
到哪里都可以找到吧