当前位置:   article > 正文

字符串匹配-KMP算法(通俗易懂)_kmp字符串匹配

kmp字符串匹配

KMP算法学了N次,忘了N次,可能因为每次都没有理解理解透彻,故写下这篇博客,帮助大家更是帮助自己能更好地理解KMP算法的原理

目录

题目背景

暴力求解

KMP优势

next数组的作用

next数组的原理

next数组的建立


题目背景

给定两个字符串A(长度为n),和字符串B(长度为m),返回在A串中,与B串相同的字串的位置。

暴力求解

很容易想到的暴力做法。i指针指向A串的字符,j指针指向B串的字符,然后一位一位比较,每次遇到不匹配的情况,i指针都要回溯,且j指针回到B串开头。时间复杂度是O(n*m)

KMP优势

可以发现,在暴力做法中,i指针回溯后的重新比较花费的大量的时间,那么KMP与之相比的优势就在于i指针不需要进行回溯,只前进不倒退,而j指针也不再每次回到B串开头,从而将复杂度降为线性。O(n+m)

next数组的作用

而KMP实现以上优势最关键的又在于next数组,正是因为他的存在,当遇到无法匹配时,i指针不用回溯,只需要将j指针转到next[j-1]即可。假设我们此时已经拥有了next数组,那么使用next数组进行匹配时的代码如下:

  1. while (i<n)
  2. {
  3. while (j && A[i] != B[j])
  4. j = nxt[j-1];
  5. if (A[i] == B[j])
  6. j++;
  7. if (j == m)
  8. {
  9. ans = i - j + 1;
  10. j = nxt[j-1];
  11. cout<<ans<<endl;
  12. }
  13. i++;
  14. }

next数组的原理

接下来我们要理解为什么可以像上述这么做。先来看看next数组的含义,next[j]表示,在B[0]~B[j]所组成的这个子串中最长公共前后缀的长度为x,通俗地说,若next[j]的值为x,则说明这个(B[0]~B[j]所组成的)子串的前x个字符和后x个字符相同。(以上提到的字串均为真字串)

以B串=ABABC为例,此时next[0]~next[4]分别就是0,0,1,2,0。

那么再假设A串=ABABABCAA为例,(下表从0开始)i和j指针从0一直匹配到3都相等,那么此时i和j都等于4,并且发现两者不相等。那么:

  1. 根据前面的匹配我们知道的,B[j]前面的j-1个字符(ABAB),一定和A[i]前面的j-1个字符相等(ABAB)。所以B[j]前面的next[j-1]个字符(AB),也一定和A[i]前面的next[j-1]个字符(AB)相等。(因为next[j-1] < j-1)
  2. 那么再根据next数组的含义我们知道,B[j]前面的next[j-1]个字符(AB),和B串开头的next[j-1]个字符(AB)相同。

综合1,2我们可得,所以A[i]前面的next[j-1]个字符(AB),和B串开头的next[j-1]字符(AB)一定相等!

所以就无需重新从头匹配,只需要从B串的第next[j-1]+1个字符开始匹配,即B[next[j-1]],(数组下标从0开始),故令 j  = next[j-1]即可。

重复上述操作,直到B[j]与A[i]相等或者j =0时停止。

next数组的建立

那么最后剩下的问题就是,我们如何建立起这个方便的next数组,也就是如何快速求出B串每一个(真)字串的最长公共前后缀。在这里,我们可以用到递推的方式。

我们利用一个i指针线性遍历每一个字符并求出next[i]。

显然,当i == 0时,next[i] = 0。

当i!=0时,已知next[i-1] = j,所以B[i]前面的j个字符一定与B串开头的j个字符相等。那么再看B串的第j+1个字符,即B[j]与当前B[i]是否相等,若相等,则next[i] = j + 1 = next[i-1] + 1。

若不相等,已知next[i-1] = j,所以

  1. B[i]前面的j个字符一定与B串开头的j个字符相等
  2. 假设B串的第j个字符,即B[j-1],它的next[j-1] = y,,故B[i]前面的y个字符一定与B串开头的y个字符相等(因为y<j)

综合1,2可知,只需要再匹配B[i]和B[y](即B[next[j-1])即可,也就是说只要令j = next[j-1],再比较B[i]和B[j]即可。

重复上述操作,直到字符匹配或者y=0时停止。建立过程代码如下:

  1. while (i<n)
  2. {
  3. j = nxt[i-1];
  4. while (j && B[i] != B[j])
  5. j = nxt[j-1];
  6. if (B[i] == B[j])
  7. nxt[i] = j+1;
  8. else
  9. nxt[i] = 0;
  10. i++;
  11. }

不明白的同学可以以ABACABAB为例,写一下过程。最后next数组的值分别是0,0,1,0,1,2,3,2

细致的同学已经发现了,建立next数组的过程和使用next数组进行匹配的过程极其相似。那是因为我们可以将建立next数组的过程也视为是一种字符串匹配,只不过匹配的是前缀串和后缀串,每次不匹配时,依旧通过已经建立起的next数组进行跳跃,思想都是一样的。

附上洛谷KMP模板题的完整代码P3375 【模板】KMP字符串匹配 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
 

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. char A[1000005],B[1000005];
  4. int nxt[1000005];
  5. int main()
  6. {
  7. scanf("%s%s",A,B);
  8. int i,j,n,m,ans;
  9. i = 0;
  10. j = 0;
  11. n = strlen(A);
  12. m = strlen(B);
  13. nxt[0] = 0;
  14. i++;
  15. while (i<n)
  16. {
  17. j = nxt[i-1];
  18. while (j && B[i] != B[j])
  19. j = nxt[j-1];
  20. if (B[i] == B[j])
  21. nxt[i] = j+1;
  22. else
  23. nxt[i] = 0;
  24. i++;
  25. }
  26. i = 0;
  27. while (i<n)
  28. {
  29. while (j && A[i] != B[j])
  30. j = nxt[j-1];
  31. if (A[i] == B[j])
  32. j++;
  33. if (j == m)
  34. {
  35. ans = i - j + 1;
  36. j = nxt[j-1];
  37. cout<<ans<<endl;
  38. }
  39. i++;
  40. }
  41. for (int i=0;i<m;i++)
  42. printf("%d ",nxt[i]);
  43. return 0;
  44. }

希望这篇文章能够帮到你!

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/900858
推荐阅读
相关标签
  

闽ICP备14008679号