kmp算法详解.docx

上传人:b****7 文档编号:9520803 上传时间:2023-02-05 格式:DOCX 页数:37 大小:721.61KB
下载 相关 举报
kmp算法详解.docx_第1页
第1页 / 共37页
kmp算法详解.docx_第2页
第2页 / 共37页
kmp算法详解.docx_第3页
第3页 / 共37页
kmp算法详解.docx_第4页
第4页 / 共37页
kmp算法详解.docx_第5页
第5页 / 共37页
点击查看更多>>
下载资源
资源描述

kmp算法详解.docx

《kmp算法详解.docx》由会员分享,可在线阅读,更多相关《kmp算法详解.docx(37页珍藏版)》请在冰豆网上搜索。

kmp算法详解.docx

kmp算法详解

引记

   此前一天,一位MS的朋友邀我一起去与他讨论快速排序,红黑树,字典树,B树、后缀树,包括KMP算法,唯独在讲解KMP算法的时候,言语磕磕碰碰,我想,原因有二:

1、博客内的东西不常回顾,忘了不少;2、便是我对KMP算法的理解还不够彻底,自不用说讲解自如,运用自如了。

所以,特再写本篇文章。

由于此前,个人已经写过关于KMP算法的两篇文章,所以,本文名为:

KMP算法之总结篇。

  本文分为如下六个部分:

1.第一部分、再次回顾普通的BF算法与KMP算法各自的时间复杂度,并两相对照各自的匹配原理;

2.第二部分、通过我此前第二篇文章的引用,用图从头到尾详细阐述KMP算法中的next数组求法,并运用求得的next数组写出KMP算法的源码;

3.第三部分、KMP算法的两种实现,代码实现一是根据本人关于KMP算法的第二篇文章所写,代码实现二是根据本人的关于KMP算法的第一篇文章所写;

4.第四部分、测试,分别对第三部分的两种实现中next数组的求法进行测试,挖掘其区别之所在;

5.第五部分、KMP完整准确源码,给出KMP算法的准确的完整源码;

6.第六步份、一眼看出字符串的next数组各值,通过几个例子,让读者能根据字符串本身一眼判断出其next数组各值。

  力求让此文彻底让读者洞穿此KMP算法,所有原理,来龙去脉,让读者搞个通通透透(注意,本文中第二部分及第三部分的代码实现一的字符串下标i从0开始计算,其它部分如第三部分的代码实现二,第五部分,和第六部分的字符串下标i皆是从1开始的)。

   在看本文之前,你心中如若对前缀和后缀这个两个概念有自己的理解,便最好了。

有些东西比如此KMP算法需要我们反复思考,反复求解才行。

个人写的关于KMP算法的第二篇文章为:

六(续)、从KMP算法一步一步谈到BM算法;第一篇为:

六、教你初步了解KMP算法、updated(文末链接)。

ok,若有任何问题,恳请不吝指正。

多谢。

第一部分、KMP算法初解

1、普通字符串匹配BF算法与KMP算法的时间复杂度比较

   KMP算法是一种线性时间复杂的字符串匹配算法,它是对BF算法(Brute-Force,最基本的字符串匹配算法的)改进。

对于给的原始串S和模式串P,需要从字符串S中找到字符串P出现的位置的索引。

BF算法的时间复杂度O(strlen(S)*strlen(T)),空间复杂度O

(1)。

KMP算法的时间复杂度O(strlen(S)+strlen(T)),空间复杂度O(strlen(T))。

2、BF算法与KMP算法的区别

   假设现在S串匹配到i位置,T串匹配到j位置。

那么总的来说,两种算法的主要区别在于失配的情况下,对

 的值做的处理:

  BF算法中,如果当前字符匹配成功,即s[i+j]==T[j],令j++,继续匹配下一个字符;如果失配,即S[i+j]!

=T[j],需要让i++,并且j=0,即每次匹配失败的情况下,模式串T相对于原始串S向右移动了一位。

   而KMP算法中,如果当前字符匹配成功,即S[i]==T[j],令i++,j++,继续匹配下一个字符;如果匹配失败,即S[i]!

=T[j],需要保持i不变,并且让j=next[j],这里next[j]<=j-1,即模式串T相对于原始串S向右移动了至少1位(移动的实际位数j-next[j] >=1),

   同时移动之后,i之前的部分(即S[i-j+1~i-1]),和j=next[j]之前的部分(即T[0~j-2])仍然相等。

显然,相对于BF算法来说,KMP移动更多的位数,起到了一个加速的作用!

 (失配的特殊情形,令j=next[j]导致j==0的时候,需要将i++,否则此时没有移动模式串)。

3、BF算法为什么要回溯

首先说一下为什么BF算法要回溯。

如下两字符串匹配(恰如上面所述:

BF算法中,如果当前字符匹配成功,即s[i+j]==T[j],令j++,继续匹配下一个字符):

     i+j(j随T中的j++变,而动)

S:

aaaacefghij

        j++

T:

aaac 

如果不回溯的话就是从下一位开始比起:

aaaacefghij

        aaac

看到上面红颜色的没,如果不回溯的话,那么从a 的下一位c 比起。

然而下述这种情况就漏了(正确的做法当然是要回溯:

如果失配,即S[i+j]!

=T[j],需要让i++,并且j=0):

aaaacefghij

  aaac

   所以,BF算法要回溯,其代码如下:

viewplain

1.int Index(SString S, SString T, int pos) {  

2.   //返回T在S中第pos个字符之后的位置  

3.   i=pos; j=1;k=0;  

4.  while ( i< = S[0] && j< = T[0] ) {  

5.      if (S[i+k] = = T[j] ) {++k;  ++j;}   //继续比较后续字符  

6.      else {i=i+1;   j=1; k=0;}      //指针回溯到 下一首位,重新开始  

7.  }  

8.  if(j>T[0]) return i;          //子串结束,说明匹配成功  

9.  else return  0;  

10.}//Index  

 不过,也有特殊情况可以不回溯,如下:

abcdefghij(主串)

abcdefg(模式串)

 即(模式串)没有相同的才不需要回溯。

4、KMP 算法思想

   普通的字符串匹配算法必须要回溯。

但回溯就影响了效率,回溯是由T串本身的性质决定的,是因为T串本身有前后'部分匹配'的性质。

像上面所说如果主串为abcdef这样的,大没有回溯的必要。

   改进的地方也就是这里,我们从T串本身出发,事先就找准了T自身前后部分匹配的位置,那就可以改进算法。

   如果不用回溯,那模式串下一个位置从哪里开始呢?

   还是上面那个例子,T(模式串)为ababc,如果c失配,那就可以往前移到aba最后一个a的位置,像这样:

...ababd...

  ababc

   ->ababc

这样i不用回溯,j跳到前2个位置,继续匹配的过程,这就是KMP算法所在。

这个当T[j]失配后,j 应该往前跳的值就是j的next值,它是由T串本身固有决定的,与S串(主串)无关。

5、next数组的含义

重点来了。

下面解释一下next数组的含义,这个也是KMP算法中比较不好理解的一点。

 令原始串为:

S[i],其中0<=i<=n;模式串为:

T[j],其中0<=j<=m。

 假设目前匹配到如下位置

              S0,S1,S2,...,Si-j,Si-j+1...............,Si-1, Si,Si+1,....,Sn

                                   T0,T1,.....................,Tj-1, Tj,..........

 S和T的绿色部分匹配成功,恰好到Si和Tj的时候失配,如果要保持i不变,同时达到让模式串T相对于原始串S右移的话,可以更新j的值,让Si和新的Tj进行匹配,假设新的j用next[j]表示,即让Si和next[j]匹配,显然新的j值要小于之前的j值,模式串才会是右移的效果,也就是说应该有next[j]<=j-1。

那新的j值也就是next[j]应该是多少呢?

我们观察如下的匹配:

     1)如果模式串右移1位(从简单的思考起,移动一位会怎么样),即next[j]=j-1,即让蓝色的Si和Tj-1匹配(注:

省略号为未匹配部分)

              S0,S1,S2,...,Si-j,Si-j+1...............,Si-1, Si,Si+1,....,Sn

                                   T0,T1,.....................,Tj-1, Tj,..........(T的划线部分和S划线部分相等【1】)

                                        T0,T1,.................Tj-2,Tj-1, .......(移动后的T的划线部分和S的划线部分相等【2】)

       根据【1】【2】可以知道当next[j]=j-1,即模式串右移一位的时候,有T[0~j-2]==T[1~j-1],而这两部分恰好是字符串T[0~j-1]的前缀和后缀,也就是说next[j]的值取决于模式串T中j前面部分的前缀和后缀相等部分的长度(好好揣摩这两个关键字概念:

前缀、后缀,或者再想想,我的上一篇文章,从Trie树谈到后缀树中,后缀树的概念)。

     2)如果模式串右移2位,即next[j]=j-2,即让蓝色的Si和Tj-2匹配    

              S0,S1,...,Si-j,Si-j+1,Si-j+2...............,Si-1, Si,Si+1,....,Sn

                                   T0,T1,T2,.....................,Tj-1, Tj,..........(T的划线部分和S划线部分相等【3】)

                                              T0,T1,...............,Tj-3,Tj-2,.........(移动后的T的划线部分和S的划线部分相等【4】)

       同样根据【3】【4】可以知道当next[j]=j-2,即模式串右移两位的时候,有T[0~j-3]==T[2~j-1]。

而这两部分也恰好是字符串T[0~j-1]的前缀和后缀,也就是说next[j]的值取决于模式串T中j前面部分的前缀和后缀相等部分的长度。

    3)依次类推,可以得到如下结论:

当发生失配的情况下,j的新值next[j]取决于模式串中T[0~j-1]中前缀和后缀相等部分的长度,并且next[j]恰好等于这个最大长度。

   为此,请再允许我引用上文中的一段原文:

“KMP算法中,如果当前字符匹配成功,即S[i]==T[j],令i++,j++,继续匹配下一个字符;如果匹配失败,即S[i]!

=T[j],需要保持i不变,并且让j=next[j],这里next[j]<=j-1,即模式串T相对于原始串S向右移动了至少1位(移动的实际位数j-next[j] >=1),

   同时移动之后,i之前的部分(即S[i-j+1~i-1]),和j=next[j]之前的部分(即T[0~j-2])仍然相等。

显然,相对于BF算法来说,KMP移动更多的位数,起到了一个加速的作用!

 (失配的特殊情形,令j=next[j]导致j==0的时候,需要将i++,否则此时没有移动模式串)。

   于此,也就不难理解了我的关于KMP算法的第二篇文章之中:

“当匹配到S[i]!

=P[j]的时候有S[i-j…i-1]=P[0…j-1].如果下面用j_next去匹配,则有P[0…j_next-1]=S[i-j_next…i-1]=P[j-j_next…j-1]。

此过程如下图3-1所示。

 当匹配到S[i]!

=P[j]时,S[i-j…i-1]=P[0…j-1]:

S:

0… i-j…i-1 i…

P:

       0…  j-1 j…

 如果下面用j_next去匹配,则有P[0…j_next-1]=S[i-j_next…i-1]=P[j-j_next…j-1]。

所以在P中有如下匹配关系(获得这个匹配关系的意义是用来求next数组):

P:

0… j-j_next .…j-1_   …

P:

        0   ….j_next-1 …

 所以,根据上面两个步骤,推出下一匹配位置j_next:

S:

0…i-j… i-j_next…  i-1      i…

P:

                   0  … j_next-1 j_next…

             图3-1求j-next(最大的值)的三个步骤

   下面,我们用变量k来代表求得的j_next的最大值,即k表示这S[i]、P[j]不匹配时P中下一个用来匹配的位置,使得P[0…k-1]=P[j-k…j-1],而我们要尽量找到这个k的最大值。

”。

     根据上文的【1】与【2】的匹配情况,可得第二篇文章之中所谓的k=1(如aaaa的形式),根据上文的【3】与【4】的匹配情况,k=2(如abab的形式)。

    所以,归根究底,KMP算法的本质便是:

针对待匹配的模式串的特点,判断它是否有重复的字符,从而找到它的前缀与后缀,进而求出相应的Next数组,最终根据Next数组而进行KMP匹配。

接下来,进入本文的第二部分。

第二部分、next数组求法的来龙去脉与KMP算法的源码

   本部分引自个人此前的关于KMP算法的第二篇文章:

六之续、由KMP算法谈到BM算法。

前面,我们已经知道即不能让P[j]=P[next[j]]成立成立。

不能再出现上面那样的情况啊!

即不能有这种情况出现:

P[3]=b,而竟也有P[next[3]]=P[1]=b。

   正如在第二篇文章中,所提到的那样:

“这里读者理解可能有困难的是因为文中,时而next,时而nextval,把他们的思维搞混乱了。

其实next用于表达数组索引,而nextval专用于表达next数组索引下的具体各值,区别细微。

至于文中说不允许P

=P[next[j]]出现,是因为已经有P

=b与S

匹配败,而P[next

]=P1=b,若再拿P[1]=b去与S

匹配则必败。

”--六之续、由KMP算法谈到BM算法。

  又恰恰如上文中所述:

“模式串T相对于原始串S向右移动了至少1位(移动的实际位数j-next[j] >=1)”。

   ok,求next数组的get_nextval函数正确代码如下:

viewplain

1.//代码4-1    

2.//修正后的求next数组各值的函数代码    

3.void get_nextval(char const* ptrn, int plen, int* nextval)    

4.{    

5.    int i = 0;     

6.    nextval[i] = -1;    

7.    int j = -1;    

8.    while( i < plen-1 )    

9.    {    

10.        if( j == -1 || ptrn[i] == ptrn[j] )   //循环的if部分    

11.        {    

12.            ++i;    

13.            ++j;    

14.            //修正的地方就发生下面这4行    

15.            if( ptrn[i] !

= ptrn[j] ) //++i,++j之后,再次判断ptrn[i]与ptrn[j]的关系    

16.                nextval[i] = j;      //之前的错误解法就在于整个判断只有这一句。

    

17.            else    

18.                nextval[i] = nextval[j];    

19.        }    

20.        else                                 //循环的else部分    

21.            j = nextval[j];    

22.    }    

23.}    

   举个例子,举例说明下上述求next数组的方法。

Sabababc

Pababc

S[4]!

=P[4]

   那么下一个和S[4]匹配的位置是k=2(也即P[next[4]])。

此处的k=2也再次佐证了上文第3节开头处关于为了找到下一个匹配的位置时k的求法。

上面的主串与模式串开头4个字符都是“abab”,所以,匹配失效后下一个匹配的位置直接跳两步继续进行匹配。

Sabababc

P     ababc

匹配成功

P的next数组值分别为-10-102

   next数组各值怎么求出来的呢?

分以下五步:

1.初始化:

i=0,j=-1,nextval[0]=-1。

由于j==-1,进入上述循环的if部分,++i得i=1,++j得j=0,且ptrn[i]!

=ptrn[j](即a!

=b)),所以得到第二个next值即nextval[1]=0;;

2.i=1,j=0,进入循环esle部分,j=nextval[j]=nextval[0]=-1;

3.进入循环的if部分,++i,++j,i=2,j=0,因为ptrn[i]=ptrn[j]=a,所以nextval[2]=nextval[0]=-1;

4.i=2,j=0,由于ptrn[i]=ptrn[j],再次进入循环if部分,所以++i=3,++j=1,因为ptrn[i]=ptrn[j]=b,所以nextval[3]=nextval[1]=0;

5.i=3,j=1,由于ptrn[i]=ptrn[j]=b,所以++i=4,++j=2,退出循环。

   这样上例中模式串的next数组各值最终应该为:

           图4-1正确的next数组各值

next数组求解的具体过程如下:

   初始化:

nextval[0]=-1,我们得到第一个next值即-1.

           图4-2初始化第一个next值即-1

   i=0,j=-1,由于j==-1,进入上述循环的if部分,++i得i=1,++j得j=0,且ptrn[i]!

=ptrn[j](即a!

=b)),所以得到第二个next值即nextval[1]=0;

           图4-3第二个next值0

  上面我们已经得到,i=1,j=0,由于不满足条件j==-1||ptrn[i]==ptrn[j],所以进入循环的esle部分,得j=nextval[j]=-1;此时,仍满足循环条件,由于i=1,j=-1,因为j==-1,再次进入循环的if部分,++i得i=2,++j得j=0,由于ptrn[i]==ptrn[j](即ptrn[2]=ptrn[0],也就是说第1个元素和第三个元素都是a),所以进入循环if部分内嵌的else部分,得到nextval[2]=nextval[0]=-1;

        图4-4第三个next数组元素值-1

   i=2,j=0,由于ptrn[i]==ptrn[j],进入if部分,++i得i=3,++j得j=1,所以ptrn[i]==ptrn[j](ptrn[3]==ptrn[1],也就是说第2个元素和第4个元素都是b),所以进入循环if部分内嵌的else部分,得到nextval[3]=nextval[1]=0;

         图4-5第四个数组元素值0

   如果你还是没有弄懂上述过程是怎么一回事,请现在拿出一张纸和一支笔出来,一步一步的画下上述过程。

相信我,把图画出来了之后,你一定能明白它的。

   然后,我留一个问题给读者,为什么上述的next数组要那么求?

有什么原理么?

    提示:

我们从上述字符串abab各字符的next值-10-10,可以看出来,根据求得的next数组值,偷用前缀、后缀的概念,一定可以判断出在abab之中,前缀和后缀相同,即都是ab,反过来,如果一个字符串的前缀和后缀相同,那么根据前缀和后缀依次求得的next各值也是相同的。

∙5、利用求得的next数组各值运用Kmp算法

   Ok,next数组各值已经求得,万事俱备,东风也不欠了。

接下来,咱们就要应用求得的next值,应用KMP算法来匹配字符串了。

还记得KMP算法是怎么一回事吗?

容我再次引用下之前的KMP算法的代码,如下:

viewplain

1.//代码5-1    

2.//int kmp_seach(char const*, int, char const*, int, int const*, int pos)  KMP模式匹配函数    

3.//输入:

src, slen主串    

4.//输入:

patn, plen模式串    

5.//输入:

nextval KMP算法中的next函数值数组    

6.int kmp_search(char const* src, int slen, char const* patn, int plen, int const* nextval, int pos)    

7.{    

8.    int i = pos;    

9.    int j = 0;    

10.    while ( i < slen && j < plen )    

11.    {    

12.        if( j == -1 || src[i] == patn[j] )    

13.        {    

14.            ++i;    

15.            ++j;    

16.        }    

17.        else    

18.        {    

19.            j = nextval[j];              

20.            //当匹配失败的时候直接用p[j_next]与s[i]比较,    

21.            //下面阐述怎么求这个值,即匹配失效后下一次匹配的位置    

22.        }    

23.    }    

24.    if( j >= plen )    

25.        return i-plen;    

26.    else    

27.        return -1;    

28.}    

我们上面已经求得的next值,如下:

        图5-1求得的正确的next数组元素各值

   以下是匹配过程,分三步:

   第一步:

主串和模式串如下,S[3]与P[3]匹配失败。

               图5-2第一步,S[3]与P[3]匹配失败

   第二步:

S[3]保持不变,P的下一个匹配位置是P[next[3]],而next[3]=0,所以P[next[3]]=P[0],即P[0]与S[3]匹配。

在P[0]与S[3]处匹配失败。

                图5-3第二步,在P[0]与S[3]处匹配失败

   第三步:

与上文中第3小节末的情况

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工程科技 > 兵器核科学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1