• KMP算法的一些注意事项


    前言:这是一个比较困难的算法,每次学每次忘,究其原因是没有彻底理解,今天我通过各种过去的学习资料和记录,详细总结一下我个人在学习中认为要注意的事项和原因。同样,由于时间和水平有限,这并非是一个教学文章,仅仅用于个人记录。

    前缀表prefix table

    kmp就离不开前缀表,我们是通过最要匹配的的子串t进行分析,得出next数组后,才能利用kmp算法进行高效率匹配。

    那其实很多时候我都只是记住算法的格式:比如abcabd,a的前缀对称数为0,ab为0,abc为0,abca为1,abcab为2,abcabd为0,然后最后一个本身的对称数不用,在最前面加一个-1。

    其实这就是next数组

    但是为什么?其实仔细想后并不难,比如说拿字符串t去匹配s,我们针对t进行了分析,哦,那假设t的第5个字符t[4]的next[4]为1,那说明什么?就是这个第五个字符的前缀t[0]t[1]t[2]t[3]对称数为1,第一个字符和第四个字符是一样的,如果假设就匹配到了第5个字符时,t[4]!=s[i],那t字符串的索引j要跳到t[next[4]]重新匹配,为什么啊,因为既然匹配到了第五个字符时才不对应,那说明第四个字符是对应的,那第四个字符又等于第一个字符,所以我们不需要去再匹配第一个字符了,而且遵循主串s的索引i不回溯原则,我们直接拿t[next[4]]也就是t[1]来和s[i]匹配。至于-1这个位置,那就i,j同时加一即可。

     具体而言:

    s:aabaaghiafa

    t:aabaaf

    那么我们可以看到f不匹配,而next[5]=2,所以拿t[2],也就是b去和s的g进行比对。

    实际上前缀串表示的是除了当前字符外,前面的字符串。ababc的c的前缀字符串就是abab,所以next[4]=2。

    注意我这里说的是对称,有的书也说最长相等前后缀(前缀不包括尾字母,后缀不包括首字母,ababc中求next[4],就是找到最长相等前后缀ab),关键明白这个意思即可。

    代码怎么写

    先看个例子:

    先求next数组

    得先求前缀表,再转化为next,转化就是所有往前挪一位,最前面再加负一。(这些都是为了后面代码好写一点)

    如果这样难理解,那么就这样想:next[0]永远等于-1。然后我们看数组索引为n的字符时,我们只在索引0到n-1的字符串里面找所谓的对称树,或者叫最长相等前后缀长度,作为next[n]的值

    关键是,我们可以这样分析,但是代码怎么写,怎么去分析出prefix[]数组来呢?知道prefix后其实也就知道了next

    所以要写代码就要发现规律:

     观察这两个,prefix的值是怎么从1变成2的呢?

    想让最长相等前后缀变长,那只能在最后一个地方加上一个B,所以匹配到索引为n的字符时,想让长度从1变成2,就需要t[n]=t[1]。再一般一点匹配到索引为n的字符时,想让长度从l变成l+1,就需要  if(t[n]==t[l+1-1]) ++len;prefix[n]=len;++n;

    else{lem=prefix[len-1];}

    ABABCABAA,设next数组的下标为k,t字符串数组的下标为j,len=9,所以当j=7的时候,k=3,t[3]=t[7],然后j++,k++,好,到最后一位了,t[8]!=t[4],这个时候就按上面的说法,回溯,k=t[k-1]

     

    今天突然对回溯有点感觉了:

    看这个评论:

     我们很朴素地去想,设next数组的下标为k,t字符串数组的下标为j,先假如此时t[k]=t[j],于是k++,j++,,额不妨设加了之后,k=3,j=9,那么此时t[k],也就是此时拿出的前缀字符串的最后一位,和t[9]不匹配,那不就说明我们拿出来的前缀太长了,我们需要拿更短一点的前缀,但是我们又不能直接说,    k--,这样很容易找到出错的例子,所以根据dp的思想,当前状态取决于上一个状态,于是乎,k=next[k-1],也就是说,至少我们知道上一次匹配的时候,k=2结尾的前缀串是可以找到匹配的后缀串的,那就一个个往前匹配,前缀也在不断缩短。

    如果不相等呢?记住,kmp的核心就是当s[i]和t[j]发生不匹配现象时,i指针不需要回溯,只需j指针回溯。而next数组其实就在实现回溯,所以这里其实不能只关注next了,而是从s,t匹配的角度去思考怎么回溯:j=next[j-1]

    现在假设t[j-1]==s[i-1],t[j]!=s[i],那说明至少前面0到j-1的字符还是可以匹配的,所以想到上面说过的,aabaaf,我们应该找到b,next[5]=2,那next[5]是怎么来的呢

    绝对超级详细的kmp算法,next数组求法,以及回溯的理解_Daaaale的博客-CSDN博客_kmp next求法

    图解KMP以及next数组的求法 - roccoshi - 博客园

    算了,真的很难记录,要自己理解,下面给两段代码:

    1. void get_prefix(char pattern[],int prefix[],int n)
    2. {
    3. prefix[0]=0;//初始化,第一个就是0,
    4. int len = 0;
    5. int i = 1;
    6. while(i < n)
    7. {
    8. if(pattern[i] == pattern[len])
    9. {
    10. len++;
    11. prefix[i] = len;
    12. i++;
    13. }
    14. else{
    15. if(len>0)
    16. {
    17. len = prefix[len-1];
    18. }
    19. else{ //初始len=0
    20. prefix[i] = len;//len这时肯定等于0
    21. i++;
    22. }
    23. }
    24. }
    25. }
    26. void move_prefix(int prefix[],int n)
    27. {
    28. int i;
    29. for(i = n-1;i > 0;i--)
    30. {
    31. prefix[i]=prefix[i-1];
    32. }
    33. prefix[0]=-1;//为了方便后面的代码
    34. }
    35. void kmp_search(char text[],char pattern[])
    36. {
    37. int n = strlen(pattern);
    38. int m = strlen(text);
    39. int *p=(int*)malloc(sizeof(int)*n);//这只是一个尝试
    40. int prefix[n+1];//创建数组并且是int,且乘以大小n
    41. get_prefix(pattern,prefix,n);
    42. move_prefix(prefix,n);
    43. int i=0;
    44. int j=0;
    45. while(i
    46. {
    47. if(j==n-1&&text[i]==pattern[j])
    48. {
    49. printf("Founf pattern at %d\n", i-j);
    50. j=prefix[j];
    51. }
    52. if(j==-1||text[i]==pattern[j])
    53. {
    54. i++;j++;
    55. }
    56. else{
    57. j=prefix[j];
    58. }
    59. }
    60. cout<
    61. for(int p=0;p
    62. {
    63. cout<' ';
    64. }
    65. }

    然后直接求开始下标为-1的Next数组:

    1. #include"bits/stdc++.h"
    2. using namespace std;
    3. const int MAXN=105;
    4. void get_next(string s,int *next)
    5. {
    6. next[0]=-1;
    7. int len=s.length();
    8. int k=-1;
    9. int i=1;
    10. while(i
    11. {
    12. if(k==-1||s[k]==s[i - 1])
    13. {
    14. k++;
    15. next[i]=k;
    16. i++;
    17. }
    18. else
    19. {
    20. k=next[k];
    21. }
    22. }
    23. }
    24. int searchkmp(string s1,string s2,int pos,int next[])//pos从0开始算
    25. {
    26. int i,j;
    27. i=pos;
    28. j=0;
    29. while(ilength()&&jlength())
    30. {
    31. if (j == n - 1 && s1[i] == s2[j]) {
    32. printf("Founf pattern at %d\n", i - j);
    33. j = next[j];
    34. }
    35. if(j==-1||s1[i]==s2[j])
    36. {
    37. j++;
    38. i++;
    39. }
    40. else
    41. {
    42. j=next[j];
    43. }
    44. }
    45. if(j==s2.length())
    46. {
    47. return i-s2.length();
    48. }
    49. else return -100;
    50. }

  • 相关阅读:
    代码随想录Day42-图论:力扣第417m、841m、463e题
    第3章 基础项目的搭建
    机器人制作开源方案 | 智能照科植物花架
    【2023.10.25练习】数据库-函数1
    vue+springboot,easyexcel的excel文件下载
    linux内核启动阶段对设备树的解析
    LeetCode //C - 61. Rotate List
    MySQL 三大日志(bin log、redo log、undo log)
    GitHub 上传本地项目代码
    mulesoft Module 9 quiz 解析
  • 原文地址:https://blog.csdn.net/keepstrivingchy/article/details/126818303