KMP算法图+文详解

来源:互联网 发布:php根目录怎么表示 编辑:程序博客网 时间:2024/06/05 07:13

前言:花了一天时间弄懂了KMP算法,翻阅的资料由于篇幅原因,晦涩难懂且五花八门。故在此写出自己的理解

一丶什么是KMP算法

         相信大家在搜索KMP关键字的时候,已经对KMP算法有所了解。简单来说,KMP算法就是一种字符串匹配算法,跟Brute-Force算法(简单匹配算法)相比,KMP算法避免了主串的回溯(后文会提到),大大提高了效率。

二、KMP算法与BF算法

       1.先讲讲BF算法

             假设主字符串s="a b a b c a b c a c b a b",现在我们要通过匹配,找出住字符串 s 中是否含有 模式串t="a b c a c"。这个过程就叫做字符串的匹配。下面是BF算法的做法

                                                   

                从s的第一位(下标0)与t的第一位字符一一匹配,当出现不匹配字符,说明此次匹配失败,故主字符串s下标回溯到与t开头字符进行匹配的位置(下标为0),下一次从当前下标的下一位(即 0+1=1)开始,从t的开头与之进行匹配。如下图所示:

                                                            

                                                                                                                         

               以此类推, 直到

                                      

                匹配成功,如果s中不含t,则s下标会超出其长度,匹配失败

        

          2.kmp算法较bf算法的改进

                  a.避免了主字符串s下标的回溯

                           首先要记住 KMP的宗旨就是拒绝s下标的回溯!(对后面的理解很重要)在上面的bf算法匹配过程中,回溯的过程耗费了绝大部分的时间,而很多回溯是没有必要的,比如:
                                          
  
                                          
                      

如果按照bf算法进行匹配,下一次匹配应从主字符串的的第二个字符 s[1] 开始与模式字符串t的第一个字符 t[0] 进行匹配。

        但是

此时,我们发现,在 t[5]之前的所有元素,都是与s中的元素一一匹配的,而此时,t[5]字符前方的相邻字符串 t[3],t[4] (即a,b) ,与模式串 t                    的前缀(即 t[0],t[1]) 相同。而s[5]前方也必有相邻字符串 s[3],s[4]=t[3],t[4]=a,b ,所以,下一次匹配,kmp算法会做如下处理

                                                                  

                                       

即当前 s 的下标不作改变,而 t 的下标改为2(因为,出现不匹配字符 t[5] 前方相邻的,与 t 前缀想同的 字符串的长度为 2)这就避免了s中下标的回溯

而此时有同学可能会问,如果不匹配位的前方相邻处没有与t的前缀想匹配的字符串,改如何处理,如:

           此时主字符串 s 的下标依然不会回溯,而是 t 下标变为0(因为与不匹配位 t[5] 相邻的且与 t 的前缀对应字符串的长度为0,稍后会解释),与s[5]继续匹配,如下

                                

          那么,中间跳过的项,会不会能够完成匹配,却被我们忽略掉了呢?事实证明不会,其原因,因这里主要说明算法的实现,故不作深究。

 到这儿,我们就知道了kmp算法对字符串匹配的大体处理过程,下面进入具体实现。


三、KMP算法的实现

      1.模式串 t 信息的保存

当 s 和 t 的匹配过程中,t 出现失配位,我们该如何得知该让 t 的下标改变为多少,从而继续进行与s的匹配呢?此时我们需要一个数据结构,来保存 t 的每一位元素回溯信息(如果这位元素成为失配元素,那么 t 的下标该回溯到多少,此时用一个数组next[],如下:

               

         为代码方便,前两位默认为-1,0。而后面的k(与 k 相同下标的元素成为失配元素,t下标应该改变为 k),在这里一一解释  

         下标[2]:元素 c 前方的子字符串,为b(此处不计开头字符),与前缀a 或 ab 都不匹配,故 k=0;

 下标[3]:元素a前方的子字符串,有bc、c,与前缀abc、ab、a都不匹配;

         下标[4]:元素b前方的子字符串,有bca、ca、a,其中,只有a 与前缀 abca、abc、ab、a 中的 a 匹配,故 k=子字符串“a”的长度=1;

 下标[5]:元素c前方的子字符串,有bcab、cab、ab、b,其中ab与前缀abcab、abca、abc、ab、a 中的 ab 匹配,故k=子字符串“ab”的长度=2

 。。。。。。。

以此类推,便可获得模式字符串 t 的子字符串信息,c++实现代码如下:

#include<iostream>using namespace std;typedef struct{char data[100];int len;}String;#define MAXSIZE 100void getIndex(String t,int next[]){int j,k;j=0;k=-1;next[0]=-1;while(j<t.len-1){if(k==-1||t.data[j]==t.data[k]){j++;k++;next[j]=k;}elsek=next[k];}}

2.kmp实现

有了上面的next[],kmp运算如下:

             


出现失配位 t[6], 参见 t 的next 信息表如下:

     即:

     next[6]中的k值为2,所以此时t 的下标(设为j),j=next[6]=2。即 s[6]与 t[2]继续匹配

    所以下一次匹配为:

         

此时出现失配位 t[2],参见next,故此时 t 下标 j=next[2]=0,匹配继续



     

出现失配位 t[0],参见next此时 t 的下标 j=next[0]=-1, 需要说明,当 j=-1 的时候,证明 s[6]不可能为匹配字符串 t 的开始字符,故作操作:s 和 t 的下标值同时加一。即 s[7]与 t[0]开始进行匹配


                  到此,所有可能出现的匹配情况已介绍完毕,按此规则一直进行,如 t 的下标 j=t的长度 匹配成功。如 s 下标超出s长度,而 t 匹配还未完成,匹配失败。具体函数返回 参见 代码:

int KMP(String s,String t){//初始化next数组int next[MAXSIZE];getIndex(t,next);int i=0;int j=0;while(i<s.len){if(s.data[i]==t.data[j]||j==-1){i++;j++;}else{j=next[j];}if(j==t.len)return i-(t.len);}//若s中不含有t,返回-1return -1;}

可算是敲完了,完全原创。如有错误偏差欢迎指出

1 0
原创粉丝点击