KMP算法笔记

蒟蒻学习了\(KMP\)算法之后的一点总结


目录:

  • 初识\(KMP\)算法

  • \(KMP\)算法的思想

  • $KMP $算法的程序实现

  • \(KMP\)算法的板子题

  • 总结


\(1.\) 初识\(KMP\)算法

​ 首先,什么是\(KMP\)算法?

\(KMP\)算法是用来处理字符串匹配问题的。也就是给你两个字符串,你需要回答:\(B\)串是否为\(A\)串的子串?(也就是\(A\)串是否包含\(B\)串)

​ 举个例子:

字符串\(A=GCAKIOIIMOICOIBO\),字符串\(B=GCAKIOI\),字符串\(C=GCtqlorz\)

我们说\(B\)\(A\)的子串,而不能说\(C\)\(A\)的子串

​ 一般来说,解决这类问题用的方法是暴力枚举

枚举从\(A\)串的什么位置起开始于\(B\)串匹配,然后验证是否完全匹配,假设\(A\)串长度为\(n\)\(B\)串长度为\(m\),那么这个算法的复杂度较为可观,是\(O(nm)\)

​ 但是······

也有例外,比如,当\(A=aaaaaaaaaaaaaaaaab\)\(B=aaaaaaab\)时,枚举方法的复杂度就不能能够满足程序执行的需求。

​ 所以有什么解决方法吗?

当然有,\(KMP\)算法就是用来解决这个问题的,这个算法可以保证在最坏情况下,时间复杂度仍能为\(O(n)\)。当然,在这里\(m\leq n\)


\(2.\) \(KMP\)算法的思想

​ 叨叨了这么多,到底\(KMP\)算法是啥样的呢?

我们假设\(A=gcgcgcggcgcak\)\(B=gcgcgak\),接下来我们根据算法流程手推一下\(KMP\)算法

​ 算法思路是这样的:

定义两个变量\(i\)\(j\),表示\(A[i-j+1···i]\)\(B[1···j]\)完全相等。也就是说,\(i\)是不断增加的,随着\(i\)的变化,\(j\)也不断地变化。且满足\(A[i]\)结尾的长度为\(i\)的字符串正好匹配\(B\)串的前\(j\)个字符

现在,我们需要检验\(A[i+1]\)\(B[j+1]\)的关系。很显然,如果\(A[i+1]=B[j+1]\)\(i\)\(j\)各加一,表示继续向前检验如果\(A[i+1]\neq B[j+1]\)\(KMP\)算法的思路是调整\(j\)的位置,也就是减小\(j\)的值,使得\(A[i-j+1···i]\)\(B[1···j]\)保持匹配并且继续程序\(j=m\)时,\(B\)就是\(A\)的子串啦

手推第一步:

根据上面的流程,我们直接跳到\(i=j=5\)的情况上

\(i\) 1 2 3 4 5 6 7 8 9 ···
\(A\) \(g\) \(c\) \(g\) \(c\) \(g\) \(c\) \(g\) \(g\) \(c\) ···
\(B\) \(g\) \(c\) \(g\) \(c\) \(g\) \(a\) \(k\) ··· ··· ···
\(j\) 1 2 3 4 5 6 7

此时,\(A[6]\neq B[6]\),也就是说,此时\(j\)不能等于5了,我们要把它改成一个更小的值\(j'\),于是我们想到将\(j\)改为3,也就是能使两个字符串继续匹配的最大\(j\)。那么我们就需要预处理出一个数组\(p[i]\)表示处理到\(j\)\(j+1\)不能匹配时要转移到的最大\(j\)

手推第二步:

继续程序,我们来到了\(i=7,j=5\)的情况上

\(i\) 1 2 3 4 5 6 7 8 9 ···
\(A\) \(g\) \(c\) \(g\) \(c\) \(g\) \(c\) \(g\) \(g\) \(c\) ···
\(B\) \(g\) \(c\) \(g\) \(c\) \(g\) \(a\) \(k\) ···
\(j\) 1 2 3 4 5 6 7

此时,\(A[8]\neq B[6]\),我们要继续程序,\(p[5]=3\),所以新的\(j\)值为3;

手推第三步:

继续程序,我们来到了\(i=7,j=3\)的情况上

\(i\) 1 2 3 4 5 6 7 8 9 ···
\(A\) \(g\) \(c\) \(g\) \(c\) \(g\) \(c\) \(g\) \(g\) \(c\) ···
\(B\) \(g\) \(c\) \(g\) \(c\) \(g\)
\(j\) 1 2 3 4 5

此时,\(A[8]\neq B[4]\),我们要继续程序,\(p[3]=1\),所以新的\(j\)值为1;

手推第四步:

​ 继续程序,我们来到了\(i=7,j=1\)的情况上···

​ 限于篇幅,下面的部分大家自己手推一下,我就不往上写了


\(3.\) \(KMP\)算法的程序实现

​ 解决了程序的流程问题,接下来我们看一下程序的核心部分,其实程序的实现并不是很难,只要理解了程序的流 程,其他都好说(其实也好背)

先是\(KMP\)算法的核心部分,即处理字符串匹配的部分

	j=0;//j的值最初为0,即从0开始匹配A串
	for(int i=0;i<n;++i)//将A串从头扫到尾
	{
		while(j>0&&B[j+1]!=A[i+1])//如果已经进行匹配并且B[j+1]不能与A[i+1]进行匹配
            j=p[j];//令当前的j值为p[j],p[j]的定义前面有讲到
		if(B[j+1]==A[i+1])//如果能继续匹配下去	
            ++j;//继续下一个匹配
		if(j==m)//如果整个B串完全匹配
		{
			printf("%d\n",i+1-m+1);//输出B串所在的位置(不要问我为什么是i+1-m+1)
			j=p[j];//继续进行匹配,因为可能还有子串被包含
		}
	}

接下来是\(KMP\)算法的另一个重要环节——处理\(p\)数组

我们可以发现,处理\(p\)数组的代码与处理字符串匹配的代码很相像(甚至只背一个部分就可以啦)。其实,\(p\)数组的处理就是一个\(B\)串自我匹配的过程,所以与处理两个字符串匹配的代码蜜汁相像

	p[1]=0;//如果B串从第一个字符开始就不能匹配,那么就将B串向右移一位
	for(int i=1;i<m;++i)//将B串从头扫到尾(注意这里的从头扫到尾并不是从0开始,而是从1开始,因为p数组的处理是B串的自我匹配。如果从1开始,那么每一个字符都会匹配。这样一来就无法求出p数组,从而影响整个程序)
	{
		while(j>0&&B[j+1]!=B[i+1])	//如果已经进行匹配并且B[j+1]不能与B[i+1]进行匹配
            j=p[j];//令当前的j值为p[j]
		if(B[j+1]==B[i+1])	//如果能继续匹配
            ++j;//将进行匹配的B串向右移一位
		p[i+1]=j;//这里是最重要的,就是说当前的第i个字符要转移到上一个能够继续匹配的地方,也是上一个能自我匹配的地方
	}

\(4.\) \(KMP\)算法的板子题

题目描述:

给出两个字符串\(s_1\)\(s_2\),其中\(s_2\)\(s_1\)的子串,求出\(s_2\)\(s_1\)中所有出现的位置。

为了减少骗分的情况,接下来还要输出子串的前缀数组\(next\)

(如果你不知道这是什么意思也不要问,去百度搜\([KMP]\)学习一下就知道了。)

题目分析:

就是\(KMP\)的板子题,这里面的\(next\)数组就是我们费尽心机求的\(p\)数组,那么在程序结束时直接输出\(p\)数组就好了\(qwq\)

#include<cstdio>
#include<iostream>
#include<cstring>

using namespace std;

char A[1000010],B[1000010];
int p[1000010];//p数组要开的和B数组一样大
int m,n,j;

int main()
{
    scanf("%s",A+1);//A+1表示从1开始读入A串,下同
    scanf("%s",B+1);
    n=strlen(A+1);
    m=strlen(B+1);
    p[1]=0;
    for(int i=1;i<m;++i)//处理p数组
    {
        while(j>0&&B[j+1]!=B[i+1])	j=p[j];
        if(B[j+1]==B[i+1])	++j;
        p[i+1]=j;
    }
    j=0;//一定别忘记将j值清零
    for(int i=0;i<n;++i)//字符串匹配
    {
        while(j>0&&B[j+1]!=A[i+1])	j=p[j];
        if(B[j+1]==A[i+1])	++j;
        if(j==m)
        {
            printf("%d\n",i+1-m+1);
            j=p[j];
        }
    }
    for(int i=1;i<=m;++i)	printf("%d ",p[i]);//输出p数组
    return 0;//恭喜你通过了这道题!!!
}

\(5.\) 总结

其实\(KMP\)算法并不是很难,但是程序中的小细节特别多。总之就是一句话,还得练。

posted @ 2019-06-18 19:35  蒟蒻hqk  阅读(173)  评论(1编辑  收藏  举报