当前位置:   article > 正文

字符串模式匹配算法——KMP算法_字符串匹配算法kmp

字符串匹配算法kmp

目录

前言

BF算法

基本思路

过程图解

代码实现

不足之处

改进方式

KMP算法

基本思路

数组next[ ]

字符串最长相等前后缀

数组next[ ]的形成原理

数组next[ ]的作用 

求next数组的代码

 KMP算法的过程图解

代码实现

KMP算法的改进

基本思路

数组nextval[ ]

数组nextval[ ]的形成原理

改进后KMP算法的过程图解

代码实现


前言

字符串是一种常见的数据结构,它由一个字符序列组成,可以被视为字符的数组或列表。每个字符都有一个对应的索引位置,允许对字符串进行访问、修改和操作。对于字符串而言,寻找字符串中子串的位置是最重要的操作之一,查找子串位置的操作通常称为模式匹配。在模式匹配中,通常将主串称为目标串,而将子串成为模式串。

本文仅介绍两种模式匹配算法,BF算法KMP算法。

规定:本文记目标串s = "abcaabbabcabaab" , 模式串t = "abcabaa" .

           用i遍历目标串s的字符,用j遍历模式串中的字符.

BF算法

BF算法,又称简单匹配算法,

基本思路

从目标串s的第一个字符开始,与模式串t的第一个字符比较,若字符匹配,则继续逐个比较后续字符;否则,从目标串回溯到第二个字符重新与模式串的第一个字符进行比较。以此类推,当目标串s的第i个字符开始与模式串t的每个字符逐个比较,若匹配成功,则返回位置i;否则目标串回溯至第i + 1个字符与模式串t逐个比较。

过程图解

代码实现

  1. int BF(string s, string t)
  2. {
  3. int i = 0, j = 0;
  4. while (i < s.size() && j < t.size())
  5. {
  6. //比较当前字符是否相等
  7. if (s[i] == t[i])
  8. {
  9. //相等时,i,j后移一位,接着比较下一位字符
  10. i++;
  11. j++;
  12. }
  13. else
  14. {
  15. //不等时,i回溯,j重置为0,重新开始遍历
  16. i = i - j + 1;
  17. j = 0;
  18. }
  19. }
  20. //判断模式串是否遍历完
  21. if (j >= t.size())
  22. //遍历完则返回t在s中的位置
  23. return (i - t.size());
  24. else
  25. //否则返回-1
  26. return (-1);
  27. }

 说明:s.size()t.size()表示目标串s和模式串t的长度,代码中以数组s[i]、t[i]的形式访问字符串中的字符.

不足之处

该算法虽然简单且易于理解,但效率不高,主要原因是目标串s中i在若干个字符比较后,若有一个字符与其不匹配,就需要进行回溯(即i = i - j + 1)。这种反反复复的回溯会大量增加程序的运行时间,而且很多运算是不必要进行的。在最好情况下时间复杂度为O(n),即子串的n个字符正好等于主串的前n个字符,而最坏的情况下时间复杂度为O(m*n)

改进方式

BF算法的高时间复杂度主要是由于其目标串s的回溯,倘若我们可以减少目标串指针的回溯次数,便可以降低其时间复杂度,而接下来的KMP算法就实现了这一需求。

KMP算法

Knuth-Morris-Pratt字符串查找算法,简称为“KMP算法”,常用于在一个文本串S内查找一个模式串P的出现位置,KMP算法由D.E.Knuth、J.H.Morris和V.R.Pratt共同提出的。KMP算法与BF算法相比有较大的改进,主要是消除了目标串指针的回溯,从而使算法的效率在某种程度上提高了。

基本思路

KMP算法是通过增加空间复杂度来减小时间复杂度。

当目标串s与模式串t进行匹配时,遇到字符不相匹配时,保持目标串s的指针i不变,将模式串t根据一个特殊的数组next[]进行回溯。

数组next[ ]

数组next[ ]是用来记录模式串t的指针j在某一位置时其之前遍历过的字符区段中最长相等的前后缀

说明:next[i]=j的含义是:下标为i 的字符前的字符串最长相等前后缀的长度为j。

字符串最长相等前后缀

示例:以模式串t = "abcabaa"为例

前缀集合 = {a,ab,abc,abca,abcab,abcaba}

后缀集合 = {a,aa,baa,abaa,cabaa,bcabaa}

则其最长相等前后缀为:a

注意:前缀和后缀不能等于字符串本身。 

数组next[ ]的形成原理

依旧以模式串t = "abcabaa"为例

当模式串t的指针j位于不同位置时

规定:next[0] = -1.

数组next[ ]的作用 

当目标串s与模式串t进行匹配,部分过程如下:

当匹配到i = 4时,目标串s的字符与模式串t的字符已经无法匹配, 此时模式串t的指针回溯 j = 1

由next数组可知,next[4] = 1,故可知当目标串s字符与模式串t字符不匹配时,模式串t的指针进行回溯,即 j = next[ j ]

数组next[ ]的作用主要有两点:

  1. next[j]的值表示下标为j的字符前的字符串最长相等前后缀的长度。
  2. next[j]的值为当字符串不匹配时,模式串指针应该回溯的位置下标。

求next数组的代码

  1. int GetNext(string t, int next[])
  2. {
  3. int j = 0, k = -1;
  4. //规定:第一个字符前无字符串,故给值-1;
  5. next[0] = -1;
  6. //因为数组的最大下标为t.size()-1,但字符串的前缀与后缀不能与本身相等
  7. //所以遍历时只能取到t.size()-2;
  8. while (j < t.size() - 1)
  9. {
  10. if (k == -1 || t[j] == t[k])
  11. {
  12. //当字符匹配时,目标串s和模式串t的指针同时后移一位
  13. j++;
  14. k++;
  15. next[j] = k;
  16. }
  17. else
  18. {
  19. //当字符不匹配时,模式串t的指针进行回溯
  20. k = next[k];
  21. }
  22. }
  23. }

 KMP算法的过程图解

代码实现

  1. int KMPPIndex(string s, string t)
  2. {
  3. int next[MaxSize], i = 0, j = 0;
  4. //获取模式串t的next数组
  5. GetNext(t, next);
  6. while (i < s.size() && j < t.size())
  7. {
  8. //判断指针是否指向第一个字符或比较当前字符是否相等
  9. if (j == -1 || s[i] == s[j])
  10. {
  11. //指针不在第一个字符时,i,j后移一位再进行比较
  12. //相等时,i,j后移一位,接着比较下一位字符
  13. i++;
  14. j++;
  15. }
  16. else
  17. {
  18. //字符不匹配时,回溯模式串t的指针j
  19. j = next[j];
  20. }
  21. //判断模式串是否遍历完
  22. if (j >= t.size())
  23. //遍历完则返回t在s中的位置
  24. return (i - t.size());
  25. else
  26. //否则返回-1
  27. return (-1);
  28. }
  29. }

KMP算法的改进

KMP算法是否可以进一步改进呢?

回看KMP算法过程图解时,我们发现在第一次匹配与第二次匹配中,模式串t的指针进行回溯时依旧没有改变目标串s中的'a'字符与模式串t中的'b'字符进行比较。倘若我们可以避免这种重复的比较,是否就进一步优化了KMP算法。

基本思路

当目标串s与模式串t进行匹配时,遇到字符不相匹配时,保持目标串s的指针i不变,将模式串t根据一个特殊的数组next[]进行回溯,若回溯后的字符与当前字符一样,则跳过当前回溯直接进行下次回溯,即构建一个特殊的数组nextval[ ] 代替next[ ] 数组的功能

通过减少模式串t的指针的回溯次数来优化KMP算法。

数组nextval[ ]

数组nextval[ ] 用于记录改进后的KMP算法的模式串t的指针回溯的下标

数组nextval[ ]的形成原理

规定:nextval[ 0 ] = -1. 

说明:当字符不匹配时,模式串t的指针 j 将进行回溯。若回溯后的字符与回溯前一致,则nextval [ j ] =  next [ next [ j ] ] ,若不一致,则nextval [ j ] = next [ j ]

改进后KMP算法的过程图解

代码实现

  1. //求取nextval[ ]
  2. int GetNext(string t, int nextval[])
  3. {
  4. int j = 0, k = -1;
  5. //规定:第一个字符前无字符串,故给值-1;
  6. nextval[0] = -1;
  7. //因为数组的最大下标为t.size()-1,但字符串的前缀与后缀不能与本身相等
  8. //所以遍历时只能取到t.size()-2;
  9. while (j < t.size() - 1)
  10. {
  11. if (k == -1 || t[j] == t[k])
  12. {
  13. //当字符匹配时,目标串s和模式串t的指针同时后移一位
  14. j++;
  15. k++;
  16. //判断指针回溯前字符是否相等
  17. if (t[j] != t[k])
  18. //不相等时,回溯到该位置
  19. nextval[j] = k;
  20. else
  21. //相等时,跳过当前回溯
  22. nextval[j] = nextval[k];
  23. }
  24. else
  25. {
  26. //当字符不匹配时,模式串t的指针进行回溯
  27. k = nextval[k];
  28. }
  29. }
  30. }
  31. //KMP
  32. int KMPPIndex(string s, string t)
  33. {
  34. int nextval[MaxSize], i = 0, j = 0;
  35. //获取模式串t的next数组
  36. GetNext(t, nextval);
  37. while (i < s.size() && j < t.size())
  38. {
  39. //判断指针是否指向第一个字符或比较当前字符是否相等
  40. if (j == -1 || s[i] == s[j])
  41. {
  42. //指针不在第一个字符时,i,j后移一位再进行比较
  43. //相等时,i,j后移一位,接着比较下一位字符
  44. i++;
  45. j++;
  46. }
  47. else
  48. {
  49. //字符不匹配时,回溯模式串t的指针j
  50. j = nextval[j];
  51. }
  52. //判断模式串是否遍历完
  53. if (j >= t.size())
  54. //遍历完则返回t在s中的位置
  55. return (i - t.size());
  56. else
  57. //否则返回-1
  58. return (-1);
  59. }
  60. }

本文仅是本人作为初学者的理解,若有不正确或不全面的方面,还望各位批评指正。

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

闽ICP备14008679号