【数据结构】KMP算法

从暴力匹配到快速匹配(KMP算法)

学习kmp算法前,首先要先了解什么是kmp算法,kmp算法具体优点是什么,kmp的主要应用方向在哪。
然后才是,代码实现

带着以上问题,我们来一步一步学习kmp算法。
问题: 给一串字符,让你从中找出与模式串相同的一段子串

例如:给这么一段字符
(主串:ABBABBABABAAABABAAA)
(模式串:ABAABABAA)
要求从主串中找出与模式串相同的一段子串

那么,一般方法,就是暴力匹配

暴力匹配(BF算法)

直接给图
image

很直观,也是多数人直接想到的方法,就是模式串中每一个字符和主串一一进行比较,如果出现不同的,模式串后移一位,重复上述操作,一直找到与模式串相同的一段子串,这种方法称为暴力匹配。
优点:方法简单,暴力。
缺点:平均时间复杂度O ( n * m )

思考:能否利用已部分匹配过的信息而加快模式串的滑动速度?
答案:KMP算法

快速匹配(KMP算法)

还是先给图
image
从上图中,箭头指向失配元素,小方框内是最大前缀和最大后缀,大方框中是要已经匹配的字符串,我们就是从这串字符做文章的
(重点!!已匹配的字符串 下文就此展开)
然后将最大前缀移动到最大后缀的位置,就完成了一次最优”滑动

那么什么是最大前缀和最大后缀呢?

最大前缀&最大后缀(如果了解,可跳过)


给图!
红色下划线:最大前缀
黑色下划线:最大后缀

image


滑动大小取决于模式串

当模式串出现失配,以此处向前从已匹配的字符串中找最大前缀和最大后缀,因为前面字符和主串字符一致,而滑动是将最大前缀移动到最大后缀的位置上最大前缀和最大后缀是从子串中找,
既然如此,

当模式串第m个字符失配,那么就从模式串串1---m中找最大前缀和最大后缀,将最大前缀移动到最大后缀的位置上
以上(从那么开始)操作,和主串无关,既

给图
image

KMP算法代码实现

由以上的理解,你大概知道了kmp算法的基本原理。那么,如何实现呢?
给代码前,了解一下“next[]”数组

next[]数组(如已了解,可跳过)


当光标指向失配字符时,我们需要移动模式串,那么移动后,我们的光标应该指向什么位置?

image

答:光标指向最大前缀长度+1的位置上
可见,模式串每个位置,都能对应这么一个数:以此向前已匹配的字符串的最大前缀长度+1,来作为光标的指示标志

如果将这么一组数存储到数组中,便可以实现模式串任意一个位置失配,滑动的距离(既光标重新指向的字符)
next[]数组就是存储这么一组能够决定光标重新指向(滑动的距离)的一组数据


行文至此,咱们全面了解了暴力匹配的思路、KMP算法的原理、流程、流程之间的内在逻辑联系,以及next 数组
那么,我们需要着手解决代码实现

直接上代码

首先实现getnext

void GetNext(char* p,int next[])  
{  
    int pLen = strlen(p);  
    next[0] = -1;  
    int k = -1;  
    int j = 0;  
    while (j < pLen - 1)  
    {  
        //p[k]表示前缀,p[j]表示后缀  
        if (k == -1 || p[j] == p[k])   
        {  
            ++k;  
            ++j;  
            next[j] = k;  
        }  
        else   
        {  
            k = next[k];  
        }  
    }  
}

到这,你可能又迷了。这代码啥意思啊,怎么和我想象的不太一样,别急,继续往下看
image

从上表,你可以跟着一步一步来,就会发现其中奥秘;

接下来,给出kmp算法

int KmpSearch(char* s, char* p)  
{  
    int i = 0;  
    int j = 0;  
    int sLen = strlen(s);  
    int pLen = strlen(p);  
    while (i < sLen && j < pLen)  
    {  
        //①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++      
        if (j == -1 || s[i] == p[j])  
        {  
            i++;  
            j++;  
        }  
        else  
        {  
            //②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]      
            //next[j]即为j所对应的next值        
            j = next[j];  
        }  
    }  
    if (j == pLen)  
        return i - j;  
    else  
        return -1;  
}  

对next的改进

先找缺陷
image
显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]

所以下一步我们应该是把j移动到第1个元素咯:

image
不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。

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

void Getnext(int next[],String t)
{
   int j=0,k=-1;
   next[0]=-1;
   while(j<t.length-1)
   {
      if(k == -1 || t[j] == t[k])
      {
         j++;k++;
         if(t[j]==t[k])//当两个字符相同时,就跳过
            next[j] = next[k];
         else
            next[j] = k;
      }
      else k = next[k];
   }
}

本文对b站天勤率辉
对https://blog.csdn.net/dark_cy/article/details/88698736多有借鉴

还有的是对代码的解释有点太少(基本没有),原因是自己对代码理解不太够,半知半解。不敢乱讲,原理懂了,代码很是神奇,有点迷,希望大神能够给一份详细的注释和解释,多谢,欢迎讨论。

原文地址:https://www.cnblogs.com/zhujiaozhu/p/15476726.html