当前位置:   article > 正文

KMP字符串模式匹配算法_算法思想是这样的:分别利用计数i和j指示主串s和模式串t中当前正待比较的字符位置

算法思想是这样的:分别利用计数i和j指示主串s和模式串t中当前正待比较的字符位置

 

 

一 简单的字符串匹配算法

1 算法思想:分别用计数指针i和j指示主串S和模式串T中当前正待比较的字符串位置。从主串S的第一个字符起,与模式串T的第一个字符比较,若相等,则继续逐个比较后续字符;否则从主串的下一个字符起,重新和模式的字符比较;以此类推,直至模式T中的每一个字符依次和主串S中的一个连续的字符序列相等,则称匹配成功,函数值为与模式T中第一个字符相等的字符在主串S中的序号,否则匹配不成功,函数值为0。

2 实现代码:

S="ababcabcac",T="abcac",结果应该为6。

  1. //简单字符串匹配算法(时间复杂度O(mn))
  2. #include <iostream>
  3. #include <string.h>
  4. using namespace std;
  5. int Index(string S, string T){
  6. int i = 1, j = 1;
  7. while(i<=S.length() && j<=T.length()){
  8. if(S[i] == T[j]){//继续比较后续字符
  9. ++i;
  10. ++j;
  11. }else{
  12. i = i-j+2;//指针回退重新开始匹配
  13. j = 1;
  14. }
  15. }
  16. if(j>T.length())
  17. return i-T.length();
  18. else return 0;
  19. }
  20. int main(){
  21. string S = "ababcabcac";
  22. string T = "abcac";
  23. cout << Index(S, T);
  24. return 0;
  25. }

运行结果:

二 KMP模式匹配算法 

简介

  KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。

提取加速匹配的信息

  上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!
  这种信息就是对于每模式串 t 的每个元素 t j,都存在一个实数 k ,使得模式串 t 开头的 k 个字符(t 0 t 1…t k-1)依次与 t j 前面的 k(t j-k t j-k+1…t j-1,这里第一个字符 t j-k 最多从 t 1 开始,所以 k < j)个字符相同。如果这样的 k 有多个,则取最大的一个。模式串 t 中每个位置 j 的字符都有这种信息,采用 next 数组表示,即 next[ j ]=MAX{ k }。
  
  加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……
  
先上代码:

  1. void Getnext(int next[],String t)
  2. {
  3. int j=0,k=-1;
  4. next[0]=-1;
  5. while(j<t.length-1)
  6. {
  7. if(k == -1 || t[j] == t[k]//若pi=pj, 则next[k+1]=next[k]+1
  8. {
  9. j++;k++;
  10. next[j] = k;
  11. }
  12. else k = next[k];//否则令k = next[k],继续循环
  13. }
  14. }

ok,下面咱们分三种情况来讲 next 的求解过程

  1. 特殊情况
    当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。

  2. 当 t[j] == t[k] 的情况
    举个栗子

观察上图可知,当 t[j] == t[k] 时,必然有"t[0]…t[k-1]" == " t[j-k]…t[j-1]",此时的 k 即是相同子串的长度。因为有"t[0]…t[k-1]" == " t[j-k]…t[j-1]",且 t[j] == t[k],则有"t[0]…t[k]" == " t[j-k]…t[j]",这样也就得出了next[j+1]=k+1。

3.当t[j] != t[k] 的情况
关于这种情况,在代码中的描述就是“简单”的一句 k = next[k]。

由第2中情况可知,当 t[j] == t[k] 时,t[j+1] 的最大子串的长度为 k,即 next[j+1] = k+1。但是此时t[j] != t[k] 了,所以就有 next[j+1] < k,那么求 next[j+1] 就等同于求 t[j] 往前小于 k 个的字符(包括t[j],看上图蓝色框框)与 t[k] 前面的字符(绿色框框)的最长重合串,即 t[j-k+1] ~ t[j] 与 t[0] ~ t[k-1] 的最长重合串(这里所说“最长重合串”实不严谨,但你知道是符合 k 的子串就行…),那么就相当于求 next[k](只不过 t[k] 变成了 t[j],但是 next[k] 的值与 t[k] 无关)!!!。所以才有了这句 k = next[k],如果新的一轮循环(这时 k = next[k] ,j 不变)中 t[j] 依然不等于 t[k] ,则说明倒数第二大 t[0~next[k]-1] 也不行,那么 k 会继续被 next[k] 赋值(这就是所谓的 k 回退…),直到找到符合重合的子串或者 k == -1。

KMP算法实现

当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。
以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例

上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。

根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1”

将模式串右移,得到上图,这样就避免了目标穿的指针回溯。

都明了之后就可以手写 KMP 的代码了

  1. int KMP(String s,String t)
  2. {
  3. int next[MaxSize],i=0;j=0;
  4. Getnext(t,next);
  5. while(i<s.length&&j<t.length)
  6. {
  7. if(j==-1 || s[i]==t[j])
  8. {
  9. i++;
  10. j++;
  11. }
  12. else j=next[j]; //j回退。。。
  13. }
  14. if(j>=t.length)
  15. return (i-t.length); //匹配成功,返回子串的位置
  16. else
  17. return (-1); //没找到
  18. }

改进后的 next 求解方法

先来看一下上面算法存在的缺陷:
在这里插入图片描述
显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]

所以下一步我们应该是把j移动到第1个元素咯:
在这里插入图片描述
不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。

显然,发生问题的原因在于t[j] == t[next[j]]。

所以我们需要添加一个判断:

  1. void Getnext(int next[],String t)
  2. {
  3. int j=0,k=-1;
  4. next[0]=-1;
  5. while(j<t.length-1)
  6. {
  7. if(k == -1 || t[j] == t[k])
  8. {
  9. j++;k++;
  10. if(t[j]==t[k])//当两个字符相同时,就跳过
  11. next[j] = next[k];
  12. else
  13. next[j] = k;
  14. }
  15. else k = next[k];
  16. }
  17. }

 

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

闽ICP备14008679号