字符串匹配之KMP算法(POJ 3461 Oulipo)

来源:互联网 发布:大唐玄怪来谒 知乎 编辑:程序博客网 时间:2024/05/14 15:23

    几种常见的字符串匹配算法

          常见的几种字符串匹配算法包括KMP算法,BM算法,Horspool算法,Sunday算法,fastsearch算法,KR算法等等。这里主要介绍一下KMP算法的匹配法则。


KMP算法思想:

        KMP算法是由D.E.Knuth与V.R.Pratt和J.H.Morris同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。作为经典的前缀匹配算法,我们来探索一下它的神秘之处。


        首先,简单粗暴的字符串匹配算法是这样的:

              

                  从左往右,text链(母链)的 i=4 和pat链(子链)的 j=4 匹配失败,遇到障碍了怎么办呢?

               

                         pat链整体右移了一步。

                         在 i=1 和 j=0 处匹配失败,又遇到麻烦了。。。

                 

                  pat链它又往后移动一步。

                 这是在 i=4 和 j=2 处匹配失败。。。

                 你猜它会怎么做?聪明!它又往后移动一步。并把这种匹配模式贯彻到底,直到 i[6..10]时text和pat完全匹配,走了6步。


         下面再来看一下KMP算法遇到麻烦是怎么解决的:

                   

                      KMP遇上麻烦了。。。

                   

                   KMP一步跳到这了。。。                  

                

                   快看,KMP又跳了!!

                       

                         它向后走了一步。

                

                          快看,AC的色彩!!它匹配成功了!!同样的问题,它只用了4步。

                 

                你是怎么做到的呀?简单粗暴以无比崇拜的眼光看着KMP


               KMP清了清嗓子,骄傲却谦卑:KMP算法的优点就是字符串匹配失败后的回溯时,利用它**next数组的记忆功能**,避免了再次匹配已经匹配过的字符,从而实现跳跃前进。

           next数组是这样来的:

                    对于模式串 pat = ABABA ,执行这段程序

void get_next(){    int i = 0, j = -1;    next[0] = -1;    while(i < lenp)    {        if(j == -1 || pat[i] == pat[j])        {            i++; j++;            next[i] = j;        }        else j = next[j];    }}
              你会得到这样一个序列:

           

             这就是next[]数组了。

         怎么样?发现了什么没?看 i = 4时, pat[2..4] == pat[0.. 2 ]。

          再换一组:

          

          看 i = 13,pat[7..13] == pat[0..6]。

          发现了吧,其实**next数组的取值完全取决于模式串**,与主串无关。并且**next[ j ]的值要取到恰到好处**,以上例来讲,

next[ 13 ] == 6,这样就使得pat[ 0..6 ](从0到next[ 13 ])这7个字符与pat[ 7..13 ](13往前数7个)这7个字符完全一样。这就是恰到好处。

          知道了这些,再结合上面的程序演示一些例子,我们会发现,我们肉眼凡胎就可以写出next数组的取值来。

             随便给你一个模式串abcabcabcd,能直接写出它的next数组来吗?


         说完了next数组,再看看KMP函数体吧!

void KMP(){    int i = 0, j = 0;    while(i < lent && j < lenp)      {        if(j == -1 || text[i] == pat[j])        {            j++; i++;        }        else j = next[j]; /*这里用到了next数组*/    }}

           通过对主串text 和模式串pat 逐字符的比较,最终得以匹配。


        各算法见仁见智的地方就是遇到“麻烦”时的应对策略。


        拿刚开始的例子从开始走到结束亲自尝试一下

    (建议回到刚开始亲自试一下)


最后,学习KMP算法要知道三句话:next数组起到记忆的作用;next数组的取值仅仅取决于模式串next[ j ]的值要取到恰到好处。多了解一些别的算法思想对KMP算法的学习也有很大的帮助。


/* POJ 3461 代码附上 */#include <stdio.h>#include <string.h>char text[1000002], pat[10002];int next[10002];int ans, lent, lenp;void get_next(){    int i, j;    i = 0; j = -1;    next[0] = -1;    while (i < lenp)    {        if (j == -1 || pat[i] == pat[j])            ++i, ++j, next[i] = j;        else            j = next[j];    }}void KMP(){    int i = 0, j = 0;    get_next();    while (i < lent)    {        if (j == -1 || text[i] == pat[j])            ++i, ++j;        else            j = next[j];        if (j >= lenp)        {            ans++;            j=next[j]; //如果不能子链重合 这边要改成j=0;        }    }}int main(){    int t;    scanf("%d", &t);    while (t--)    {        ans = 0;        scanf("%s", pat);//子链        scanf("%s", text);//母链        lent = strlen(text);        lenp = strlen(pat);        KMP();        printf("%d\n", ans);    }    return 0;}

0 0