$KMP$算法

$KMP$算法详解

    解决字符串匹配这类问题,通常我们的方法是枚举从$A$串的什么位置起开始与B匹配,然后验证是否匹配。假如$A$串长度为$n$,$B$串长度为$m$,那么这种方法的复杂度是O (mn)的。虽然很多时候复杂度达不到mn(验证时只看头一两个字母就发现不匹配了),但我们有许多“最坏情况”,比如,$A=aaaaaaaaaaaaaaaaaaaaaaaaaab$,$B=aaaaaaaab$。我们将介绍的是一种最坏情况下$O(n)$的算法(这里假设 $m<=n$),即传说中的$KMP$算法。
    之所以叫做KMP,是因为这个算法是由$Knuth$、$Morris$、$Pratt$三个提出来的,取了这三个人的名字的头一个字母。
    KMP的代码很短,我们在这里给出:

int j=0;
for(int i=1;i<=len1;i++)
{
	while(j>0&&b[j+1]!=a[i]) j=p[j];
	if (b[j+1]==a[i]) j++;
	if (j==len2) {ans++;j=p[j];}
}

    最后的$j=p[j]$是为了让程序继续做下去,因为我们有可能找到多处匹配。
    这个程序或许比想像中的要简单,因为对于i值的不断增加,代码用的是for循环
。因此,这个代码可以这样形象地理解:扫描字符串A,并更新可以匹配到B的什么位置。

    现在,我们还遗留了两个重要的问题:一,为什么这个程序是线性的;二,如何快速预处理P数组。
    为什么这个程序是$O(n)$的?其实,主要的争议在于,$while$循环使得执行次数出现了不确定因素。我们将用到时间复杂度的摊还分析中的主要策略,简单地说就是通过观察某一个变量或函数值的变化来对零散的、杂乱的、不规则的执行次数进行累计。$KMP$的时间复杂度分析可谓摊还分析的典型。我们从上述程序的$j$值入手。每一次执行while循环都会使j减小(但不能减成负的),而另外的改变j值的地方只有第五行。每次执行了这一行,j都只能加1;因此,整个过程中j最多加了$n$个$1$。于是,$j$最多只有$n$次减小的机会($j$值减小的次数当然不能超过$n$,因为$j$永远是非负整数)。这告诉我们,$while$循环总共最多执行了$n$次。按照摊还分析的说法,平摊到每次$for$循环中后,一次$for$循环的复杂度为$O(1)$。整个过程显然是$O(n)$的。这样的分析对于后面P数组预处理的过程同样有效,同样可以得到预处理过程的复杂度为$O(m)$。   

 $code:$

for (int i=2;i<=len2;i++)
{
	while(j&&b[i]!=b[j+1]) j=p[j];
	if(b[j+1]==b[i])j++;
	kmp[i]=j;
}

    最后补充一点:由于KMP算法只预处理B串,因此这种算法很适合这样的问题:给定一个B串和一群不同的A串,问B是哪些A串的子串。

猜你喜欢

转载自www.cnblogs.com/lzxzy-blog/p/10789189.html