当前位置:   article > 正文

KMP字符串匹配算法(看完必懂!!!)_kmp算法通配符特征码

kmp算法通配符特征码

KMP算法的原理

这个算法理解起来比较复杂,看了网上很多帖子,写的都很乱,不容易理解。现在结合看过的一些书和视频写一些好理解的笔记,希望能给大家带来帮助:

总的思想还是想要回退的时候能尽量偷懒,利用已知的信息,阮老师讲的很清楚:http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html

也就是说在每次不匹配发生回退的时候,尽量能让之前比较过的一致的字符能够不用再重复匹配。

 

KMP算法的实质是,有时候,字符串头部和尾部会有重复。比如,"ABCDAB"之中有两个"AB",那么它的"部分匹配值"就是2("AB"的长度)。搜索词移动的时候,第一个"AB"向后移动4位(字符串长度-部分匹配值),就可以来到第二个"AB"的位置。

这就引出了最长的真前缀和 真后缀匹配长度的问题,而基于模式串自身的自匹配性,这个长度在给出模式串的时候已经能算出来。

我们把模式串首先遍历一遍,将每个位置之前字串的最大真前缀和真后缀长度事先存在next数组中,这样每当发生一次失配时,在这个数组中查找这个最长的匹配长度,然后移动到这个最长匹配位置。举个例子:

当上面的情况发生空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应next数组中的"部分匹配值"为2,即“AB”,因此按照下面的公式算出向后移动的位数:

  移动位数 = 已匹配的字符数 - 对应的部分匹配值

因为 6 - 2 等于4,所以将搜索词向后移动4位。

怎么求next数组?

 先来考察这样的一个场景,在字符串P[i]和模式串P[j]处发生了失配,KMP会去查表,取出next[j],即用P[t]去取代P[j],让P[t]继续与之前的P[i]相对齐。那么为什么会选定这样的t呢?或者说这样的t又具备哪些必要条件呢?

 经过分析我们不难发现,选择这个t的必要条件是:在P[j]之前已经适配的子串中,必须有一个长度为t的前缀和一个长度为t的后缀完全匹配,也就是说这个字串的首部和尾部具有一定的相似性。选择这个t必要条件也就可以表示为:

P[0,t) == P[j-t,j)

 将满足下述条件的所有t筛选出来,也就可以得到一个候选集合:

 也就是在P[j]的前缀P[0,j)中,所有匹配真前缀和真后缀的长度

在发生一次失配时,也只有来自这个集合中的t,才有资格来作为下一轮的对齐位置。

而next表,其实就是:

那么我们怎么具体怎么求解next数组呢?这里不妨采用递推策略:

 分析不难得出如下结论:

 当且仅当P[j]和它的替代者P[ next[j] ]相等时等号成立。

 也就是说当模式串的某一字符和它的继任者在这一位置相等时,如上图,那么next[j+1] = next[j] +1;(最长匹配长度变长了一位)

那么如果二者不相等呢?怎么去递推?

我们还是按照原有思路,当一次失配发生,我们调用next数组中的对应值,往最大匹配的位置上滑动。那么如果还是不匹配,我们就在新的模式串位置再次调用next中的值,即next[ next[ j ] ]。这个过程可能持续多步,直到匹配为止。见下图:

因为next[ j ]是表示真前缀和真后缀中的最长匹配长度,故next[j]<j(严格小于) ,故上面这个候选序列只会严格递减,直到下图中的最后面的情况:

在这种情况下,通常会出现问题。因为接下来和P[j]比对的那个字符根本就无从谈起。这时候就是哨兵大显身手的时候了,KMP巧妙的借助了"哨兵"。即让next[0]=-1;在模式串的最前面加上-1的通配符,它和任意字符都可以匹配。因此每当第一个字符不能匹配时,我们就用哨兵来匹配。

分析上述过程,我们发现这其实就是模式串不断自匹配的过程。

代码实现

分析完可以开始写代码了,事实上,求next数组的代码和KMP代码几乎一模一样。差别在于要设置个哨兵,以及只传一个模式串参数。我们只需要记住其中一个就够了。

根据《数据结构(C++版)》KMP算法的伪代码可以用如下伪代码表述:

  1. 1. 在串S和串T中分别设置比较的起始下标i和j;
  2. 2. 重复下述操作,直到S或T的所有字符均比较完毕;
  3. 2.1 如果S[i]等于T[j],继续比较S和T的下一对字符;
  4. 2.2 否则将下标j回溯到next[j]的位置,即j = next[j];
  5. 2.3 如果j等于-1,则将下标i和j分别加1,准备下一趟比较;
  6. 3. 如果T中所有字符均比较完毕,则返回匹配的i-j;
  7. 否则返回-1;

至此,我们搞清楚了算法思路,以下是代码:

  1. //求Next数组
  2. void getNext(char * p, int * next)//只需要传入模式串,模式串不断自匹配,既作为母串,又作为匹配串
  3. {
  4. next[0] = -1;//初始化哨兵
  5. int i = 0;
  6. int j = -1;//j代表下方模式串中的最右匹配位置,初始化为哨兵位置
  7. while (i < strlen(p))
  8. {
  9. if (j == -1 || p[i] == p[j])
  10. {
  11. ++i;
  12. ++j;
  13. next[i] = j;//当前位置匹配,next[j+1]=next[j]+1
  14. }
  15. else
  16. j = next[j];//将当前位置更新为next[j],再次比对
  17. }
  18. }
  19. //KMP算法
  20. int KMP(char * t, char * p)
  21. {
  22. int i = 0;
  23. int j = 0;
  24. while (i < strlen(t) && j < strlen(p))
  25. {
  26. if (j == -1 || t[i] == p[j])
  27. {
  28. i++;
  29. j++;
  30. }
  31. else
  32. j = next[j];
  33. }
  34. if (j == strlen(p))
  35. return i - j;
  36. else
  37. return -1;
  38. }

也可以参考下这篇文章:https://blog.csdn.net/x__1998/article/details/79951598

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

闽ICP备14008679号