当前位置:   article > 正文

KMP算法图文详解_kmpalgorithm

kmpalgorithm

简介:

        Knuth-Morris-PrattKMP算法or字符串查找算法)可在一较长串S内查找一子串P出现的位置,KMP算法利用最长公共前后缀的特性以避免重新检索先前配对的字符串,提高算法效率。

——KMP算法最终由Knuth-Morris-Pratt三人于1977年联合发表

朴素算法(Brute Force Algorithm):

代码示例:

  1. #include<iostream>
  2. #include<cstring>
  3. //i和j下标从0开始的情况:
  4. using namespace std;
  5. const int Max = 1e3 + 10;
  6. int main() {
  7. char s[Max]={}, p[Max]={};
  8. cout << "请输入文本串S" << endl;
  9. cin >> s;
  10. cout << "请输入模式串P" << endl;
  11. getchar();
  12. cin >> p;
  13. int i = 0, j = 0;
  14. int sLen = strlen(s), pLen = strlen(p);
  15. while (i < sLen && j < pLen) {
  16. if(s[i] == p[j]){//如果匹配成功,i和j同步后移
  17. i++;
  18. j++;
  19. }
  20. else {//如果失配,i回溯,j归零
  21. i = i - (j - 1);
  22. //注意,i回溯到了最初始匹配成功的i的下一位;
  23. //最初始匹配成功:上一次匹配失败的下一位的i;
  24. j = 0;
  25. }
  26. }
  27. //如果匹配成功,则j等于pLen;
  28. if (j == pLen) cout << "匹配成功,物理位置为:" << i - j
  29. <<" " << "逻辑位置为:" << i - j + 1 << endl;
  30. else cout << "抱歉,匹配失败!" << endl;
  31. return 0;
  32. }

例如:查找串S为:ABCABCDAB 模式串P为:ABCD

注意:这里规定二者下标均从0开始

查找过程中:下标i从0开始,P串下标j从0开始;(下文会详解)

        

要想查找P在S中的位置,该如何查找呢?

假设此时:S和P都查找到了下标为2(逻辑地址,i = 1)的位置:因为S[i] == P[j],故i++,j++;

假设此时:S和P都查找到了下标为6的位置:但S[i] != P[j]; 此时:i = 5,j = 5

按照朴素算法,应该这样操作:令 i = i - j + 1 ,j = 0;(i的回溯过程)

此时 i = 5 - 5 + 1 = 1,i = 1,j = 0 ,相当于模式串P右移:S[i] != P[j];

此时 i = 1 - 0 + 1 = 2,i = 2,j = 0 ,相当于模式串P右移:S[i] != P[j];

此时 i = 2 - 0 + 1 = 3,i = 3,j = 0 ,由于:S[i] == P[j];   则:i++,j++;

 直到:i = 6 , j = 3;由于S[i] != P[j]; 此时 i = 6 - 3 + 1 = 4 , j = 0 ;相当于模式串P右移:

以此类推,直到 i = 9 ,由于 j = 5 即 N - 1 ; 所以查找到模式串;

KMP算法(KMP Algorithm):

代码示例:

  1. #include<iostream>
  2. #include<cstring>
  3. using namespace std;
  4. //求next数组:
  5. void GetNext(char* p, int next[]) {
  6. int pLen = strlen(p);
  7. next[0] = -1;
  8. int k = -1, j = 0;//p[k]表示前缀,p[j]表示后缀
  9. while (j < pLen - 1) {
  10. if (k == -1 || p[j] == p[k]) {
  11. j++;
  12. k++;
  13. next[j] = k;
  14. }
  15. else {
  16. k = next[k];//模式串的自我匹配过程
  17. }
  18. }
  19. }
  20. const int Max = 1e3 + 10;
  21. int main() {
  22. char s[Max] = {}, p[Max] = {};
  23. int next[Max] = {};
  24. GetNext(p, next);
  25. cout << "请输入文本串S" << endl;
  26. cin >> s;
  27. cout << "请输入模式串P" << endl;
  28. getchar();
  29. cin >> p;
  30. int i = 0, j = 0;
  31. int sLen = strlen(s), pLen = strlen(p);
  32. while (i < sLen && j < pLen) {
  33. if (s[i] == p[j] || j == -1) {//如果匹配成功 or j == -1,i和j同步后移
  34. i++;
  35. j++;
  36. }
  37. else {//如果失配,i不变,j = next[j];
  38. j = next[j];//相当于模式串p右移了j-next[j]位;
  39. }
  40. }
  41. //如果匹配成功,则j等于pLen;
  42. if (j == pLen) cout << "匹配成功,物理位置为:" << i - j
  43. << " " << "逻辑位置为:" << i - j + 1 << endl;
  44. else cout << "抱歉,匹配失败!" << endl;
  45. return 0;
  46. }

引言:

因此我们发现,朴素算法的效率比较低,尤其是第一步:

我们发现在ABCAB(下标1-5)中:

AB(下标12)作为前缀与作为后缀的AB(下标45)共同构成了最长(长度为2)的公共前后缀

因此,我们只需要:令 j = 2 ,相当于向右移动模式串3位(j = 5 ,j - 2 = 3);

这样以来,大大节省了匹配速度,面对失配情况,我们可以通过模式串P对应位的最长公共前后缀的长度,进行快速移位。不再需要像朴素算法那样,进行i的回溯和j的归零。

KMP算法的难点在于next数组的理解和构建

引入next数组:

引言我们可知:模式串P的第5位:对应的最长公共前后缀为2;

因此我们可以先求得该位置的最长公共前后缀:

模式串P[N]ABCABD
下标值123456
最大公共前后缀000120
next数组-100012

前缀:例如:ABCDA :不包括最后一个字符A的所有字符组合:A,AB,ABC,ABCD

后缀:例如:ABCDA :不包括最前一个字符A的所有字符组合:A,AD,ADC,ADCB

则,ABCDA的公共前后缀为:A,其中最长的为A,故ABCDA的最长公共前后缀为A;

显而易见,next 数组相当于“最大长度值” 整体向右移动一位,然后初始值赋为 -1。

KMP查找模式串:

由引言和引入next数组可知:遇到s[i]  != p[j]的情况,我们可以令j = next[j];

令j = next[j] ,相当于保持查找串S不动,模式串P右移了(j - next[j])位;

相当于前缀为1,后缀为2,将1的位置平移至3的位置; 

next[5] = 2,令 j = next[5] = 2,则模式串P右移了(5-2)位;

KMP查找代码如下:

  1. while (i < sLen && j < pLen) {
  2. if (s[i] == p[j] || j == -1) {//如果匹配成功 or j == -1,i和j同步后移
  3. i++;
  4. j++;
  5. }
  6. else {//如果失配,i不变,j = next[j];
  7. j = next[j];//相当于模式串p右移了j-next[j]位;
  8. }
  9. }

求解next数组的值:

求解next数组和KMP查找差不多,只不过KMP查找是S[i]在P[j+1]之间查找;

求解next数组是模式串的自我匹配过程

求解next数组代码如下:

  1. void GetNext(char* p, int next[]) {
  2. int pLen = strlen(p);
  3. next[0] = -1;
  4. int k = -1, j = 0;//p[k]表示前缀,p[j]表示后缀
  5. while (j < pLen - 1) {
  6. if (k == -1 || p[j] == p[k]) {
  7. j++;
  8. k++;
  9. next[j] = k;
  10. }
  11. else {
  12. k = next[k];//模式串的自我匹配过程
  13. }
  14. }
  15. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/662129
推荐阅读
相关标签
  

闽ICP备14008679号