当前位置:   article > 正文

kmp算法(小白整理)

kmp算法

kmp算法   分类:字符串  

/*

这里推荐一位B站up主的视频,我的思路也借鉴于他。传送门:

理论篇:

帮你把KMP算法学个通透!(理论篇)_哔哩哔哩_bilibili

代码实现篇:

帮你把KMP算法学个通透!(求next数组代码篇)_哔哩哔哩_bilibili

*/

目录

初遇kmp

1.正常思路:暴力枚举

2.kmp算法的大致思路

3.kmp算法的next数组引入

4.kmp算法的具体实现


初遇kmp

kmp算法 通常的用途是:给你一段长字符串(母串),再给你一段短字符串(子串),让你找长字符串中有多少个短字符串出现。/或者让你在长字符串中,找满足条件的子串。

比如现在有一个母串和一个子串,让你找母串中子串第一次出现的位置。

文本串(母串)s1    aabaabaaf  

模式串(子串)s2    aabaaf       

为了从其他文章过来的同学更好的计算时间复杂度,这里用 n表示s1的长度,m表示s2的长度。

1.正常思路:暴力枚举

两层for循环,忽略时间复杂度O(n*m),肯定能走过每一种情况。

  1. for(int i=0;i<s1.size();i++) //这里s1是string类型,如果是char数组的话,用strlen(s1)表示长度一样的。
  2. {
  3.      for(int j=0;j<s2.size();j++)
  4.      {
  5.          if(s1[i+j]!=s2[j]) //j是比较时s2字符串的下标,i+j是s1比较到的下标位置
  6.              break;
  7.      }
  8. }

2.kmp算法的大致思路

如果暴力枚举的话,字符串长度很短的情况,是可以卡过去的,但是当长字符串到了1e6~1e7的长度左右,很容易被卡时间。

所以我们考虑,有没有什么办法来优化呢?我们可以发现,在比较两字符串是否相等的过程中,有很多重复无意义的比较。

比如上文给出的字符串s1和s2,我比较到i=0,j=6的字符串,能发现s1中的 aabaab前5位 与s2的 aabaaf前5位 相同,但是第6位 b!=f,所以i++,j=0 再次从头比较。

但是聪明的你们肯定也发现了,我们第一次比较,虽然没有找出子串,但是在s1中找出了两个aab,都与s2的前三位aab相等。

如果我们想找完全相等的字符串的话,第一个aab已经被否定,那我们为什么不直接从第二个aab再往后找呢?

这就是kmp算法相比正常暴力枚举所优化的地方。

3.kmp算法的next数组引入

为了实现刚才那种人能想到,但计算机想不到的思路,我们需要先对子串进行处理

大多数文章中用 next数组/pre数组 来表示子串的前缀表,这里我怕大家混淆其它算法中的next含义,我用kmp数组来表示

真前后缀概念:不用概念,一看就懂。注意一下真前后缀不包含自身。

即aabaaf的前缀子串有:a,aa,aab,aaba,aabaa   后缀子串有:f,af,aaf,baaf,abaaf

从i=0到i=5比较,求每个前缀子串的最长相等前后缀的长度

               简单说明:比如aabaa串,最长的一段,是它的前缀aa和后缀aa相等,长度为2

a0无前缀 无后缀    长度为0
aa1前缀a 后缀a    长度为1   a
aab0前缀aa 后缀ab    长度为0
aaba1前缀aab,后缀aba    长度为1a
aabaa2前缀aaba 后缀abaa   长度为2aa
aabaaf0前缀aabaa 后缀abaaf   长度为0

可得:前缀表(临时数组) 010120  //kmp数组

  1. //伪代码如下:
  2. int kmp[1000010]; //全局变量初值为0,如果有多组测试样例,记得每次初始化
  3. void getkmp(子串s)
  4. {
  5.     memset(kmp,0,sizeof(kmp));
  6.     初始化  j=0;  
  7.     kmp[0]=0;
  8.     for(i=1; i<s.size(); i++)   //i继续匹配,for循环捋一遍子串
  9.     {
  10.         前后缀不相同
  11.         while(s[i]!=s[j] && j>0) //if会错,需要连续回退,0是边界
  12.              j=kmp[j-1];
  13.         前后缀相同
  14.         if(s[i]==s[j])
  15.             kmp[i]=++j;  //j继续匹配,并且kmp[i]赋值
  16.     }
  17. }

4.kmp算法的具体实现

学会如何得到kmp数组之后,那找到了前缀表有什么用呢?

kmp数组,我们能得到子串的最长相等前后缀,我们能根据它相等的部分,直接从相等部分的下一位开始比较。

比如 母串aabaabaaf,子串aabaaf

我们暴力枚举,当i=0,j=6比较到aabaab与aabaaf并不相等时,我们不用再让i=1,j=0重新比较。

我们重新定义i和j,让i指向母串s1的下标,j指向子串s2的下标。

头脑风暴,前方高能


我们这样移动i和j,i和j同时移动并对比,i=5并且j=5时,因为b!=f,所以我们将j回退,考虑我们如果能比较到f这个位置,那b前面(母串)一定存在aa和aabaaf(子串)中f前的aa重复,而aab!=aaf,则我们回退到上一个aa(子串)的位置。

因为之前计算的kmp数组,当子串为aabaa时,最长相等前后缀为2,即当下标为2时,前边的aa相当于比完了,所以j回退到2,i仍然是5,s2[j]与s1[i]再进行比较,我们就能直接比较出aab(母串)与aabaaf(子串)前面相等,于是i++,j++,继续向后比较,直到在母串中找出子串。

考虑一种情况,如果母串是aac,子串aabaaf,我们j回退到2,s1[i]=’c’ , s2[j]=’b’,还是不相等怎么办?那就和之前一样处理,找c前面的子串对应的前缀表,可以看到aa对应值为1,所以j回退到1,再次进行比较。如果j一直回退到0,还没有找到相等的部分,那就说明已经退到边界了,得重新比较了。


如果光看分析看不懂的话,建议结合代码,自己拿张纸画出子串和母串,然后手动模拟观察i和j的位置。

  1. //代码实现部分:
  2.     for(int i=1,j=0;i<s2.size();i++) //得到kmp数组,s1长字符串(母串),s2短串(子串)
  3.     {
  4.         while(j && s2[i]!=s2[j])
  5.         {
  6.             j=kmp[j-1];
  7.         }
  8.         if(s2[i]==s2[j])
  9.         {
  10.             kmp[i]=++j;
  11.         }
  12. }
  13. for(int i=0,j=0;i<s1.size();i++)     //在s1串中找到第一个s2串
  14. {
  15.         while(j && s1[i]!=s2[j])
  16.         {
  17.             j=kmp[j-1];
  18.         }
  19.         if(s1[i]==s2[j])
  20.         {
  21.             j++;
  22.         }
  23.         if(j==s2.size())
  24.         {
  25.             ans=i;
  26. break;
  27.         }
  28.     }

板子题:P3375 【模板】KMP字符串匹配 -> kmp模板题(洛谷)

AC代码如下:

  1. /*
  2. P3375 【模板】KMP字符串匹配
  3. https://www.luogu.com.cn/problem/P3375
  4. 思路:此题要求不止找到一个出现的子串,而是要找到所有出现过的子串,所以我们需要根据题意,修改查找部分的代码。
  5. */
  6. #include<bits/stdc++.h>
  7. using namespace std;
  8. const int N=1e6+5;
  9. typedef long long ll;
  10. string s1,s2;
  11. int kmp[N];
  12. int main()
  13. {
  14. cin>>s1>>s2;
  15. //printf("%d %d\n",s1.size(),s2.size());
  16. for(int i=1,j=0;i<s2.size();i++)
  17. {
  18. while(j && s2[i]!=s2[j])
  19. {
  20. j=kmp[j-1];
  21. }
  22. if(s2[i]==s2[j])
  23. {
  24. kmp[i]=++j;
  25. }
  26. }
  27. for(int i=0,j=0;i<s1.size();i++)
  28. {
  29. while(j && s1[i]!=s2[j])
  30. {
  31. j=kmp[j-1];
  32. }
  33. if(s1[i]==s2[j])
  34. {
  35. j++;
  36. }
  37. //if(i-j==-1)
  38. // printf("%d %d\n",i,j);
  39. if(j==s2.size())
  40. {
  41. printf("%d\n",i-j+2);
  42. j=kmp[j-1];
  43. }
  44. }
  45. for(int i=0;i<s2.size();i++)
  46. printf("%d ",kmp[i]);
  47. return 0;
  48. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/662084
推荐阅读
相关标签
  

闽ICP备14008679号