原文地址: https://www.cnblogs.com/Syhawk/p/4077295.html

 BF、KMP、BM、Sunday算法讲解

  字串的定位操作通常称作串的模式匹配,是各种串处理系统中最重要的操作之一。

  事实上也就是从一个母串中查找一模板串,判定是否存在。

  现给出四种匹配算法包括BF(即二维循环匹配算法)、KMP、BM、Sunday算法,着重讲KMP算法,其他算法尽量详细讲解,有兴趣的读者可自行查找其它相关资料了解其它算法,当然本文也会推荐一些网址供读者参考。

  事实上本博文也是作者阅读了其它博文,然后根据自己的在理解过程中遇到的问题加以阐述,总结而来的,尤其是多次阅读了July的博文。

  本文可能会给一部分读者阅读带来不便,所以在本文开始部分就推荐读者相关博文,若读者已经掌握了相关算法,就不要浪费时间继续浏览本博文了,干点其他有意义的事吧:

  July博文(强力推荐)http://blog.csdn.net/v_july_v/article/details/7041827

  注:为方面书写,S称作母串,T称作模板串

  一、BF:二维循环匹配算法

    算法比较简单,不再给予相关解释,直接给出代码,如下:

    

 1   int Search(const string& S, const string& T) {
 2   
 3     int i = 0;
 4     int j = 0;
 5   
 6     while(S[i] != ‘\0’ && T[j] != ‘\0’) {
 7       if(S[i] == T[j]) {
 8         ++ i;
 9         ++ j;
10       } else {
11         i = i - j + 1;
12         j = 0;
13       }
14     }
15   
16     if(T[j] == ‘\0’)
17       return i - j;
18   
19     return -1;
20   }

     从代码可以看出,此算法的时间复杂度为O(),相当耗时,一般不采用此算法。但是读者一

  定要明确知道此算法的运行过程,KMP算法就是在此基础上改进而来的。

  二、KMP算法

    此算法是D.E.Knuth与V.R.Pratt 和J.H.Morris同时发现的,取三人名字首字母便得到了KMP,

  也称作为克努特-莫里斯-普拉特操作。此算法的时间复杂度为O(n+m),n为S的长度,m为T的长

  度。

    假设S为

      BBCABCDABABCDABCDABDE

    T为

      ABCDABD

    如果按照二维匹配算法,那么过程如下:

    第一步,从S与T首字符开始匹配

      B B C A B C D A B A B C D A B C D A B D E

      ↨

      A B C D A B D

    第二步,匹配到此处(为方便说明,省去了不必要的匹配过程)

      B B C A B C D A B A B C D A B C D A B D E

          ↨

           A B C D A B D

    第三步,根据首字符相匹配可得,可以继续执行T遍历,查找S中的子串

      B B C A B C D A B A B C D A B C D A B D E

                    ↨

           A B C D A B D

    到了此处我们发现出现了不匹配现象,如果按照二维循环匹配算法,那么下一步必然会这样:

    (1)

      B B C A B C D A B A B C D A B C D A B D E

                  ↨

                  A B C D A B D

    紧接着又会出现不匹配,直到运行到下面的结果:

    (2)

      B B C A B C D A B A B C D A B C D A B D E

                    ↨

                    A B C D A B D

    让我们算一下,从第三步下标的位置到(2)这一步T的下标共移动了几次呢??答案是6次

  (但愿我没算错)!!如果我们直接从第三步移动到(2)这一步是不是只需要一次就可以了呢

  ,这样算下去的话,能减少多少不必要的匹配时间!!

    因此,KMP算法诞生了,按照以上的步骤(运行到第三步后直接调转到(2)),给出相关代码:

 

 1   int KMP_Search(const string& S, const string& T) {
 2   
 3     int i = 0;
 4     int j = -1;
 5     int sLen = S.size();
 6     int tLen = T.size();
 7   
 8     while(i < sLen && j < tLen) {
 9       if(j == -1 || S[i] == T[j]) {
10         ++ i;
11         ++ j;
12       } else {
13         j = next[j];
14       }
15     }
16   
17     if(j == tLen)
18       return i - j;
19   
20     return -1;
21   }

    以上代码可以看出,关键的实现在于next数组,所以读者会问next数组里面保存了什么,能

  够实现这么强大的功能??下面将给予解答……

    事实上next[k]数组保存了T前k-1位相等的后缀和前缀的最大长度,next[0]为-1。

    以T为”ABCDABD”为例,即:

k值

前缀

后缀

最长相等的前缀与后缀

next[k]

0

-1

1

0

2

A

B

0

3

A、AB

C、BC

0

4

A、AB、ABC

D、CD、BCD

0

5

A、AB、ABC、ABCD

A、DA、CDA、BCDA

A

1

6

A、AB、ABC、ABCD、ABCDA

B、AB、DAB、CDAB、BCDAB

AB

2

7

A、AB、ABC、ABCD、ABCDA、ABCDAB

D、BD、ABD、DABD、CDABD、BCDABD

0

    接着,读者可能会问为什么要保存它??

    不得不说,上述表格中的数据确实说明不了什么,而且还有可能使读者更加困惑,不过没关

  系,请耐心继续往下看,一会就会明白了……

    借用第三步得到的状态:

      B B C A B C D A B A B C D A B C D A B D E

                    ↨

           A B C D A B D

    S与T分别匹配到A与D处,如果按照二维匹配算法,下一步就会这样:

      B B C A B C D A B A B C D A B C D A B D E

            ↨

         A B C D A B D

    S的’B’与T的’A’匹配辨别,事实上就是(加重部分)的匹配,

      B B C A B C D A B A B C D A B C D A B D E

                             ↨

           A B C D A B D

     不成功匹配字符串”BCDABA”中的”BCDAB”正好是T中匹配成功的部分,也就是T中

  ”ABCDAB”的字串,也是”ABCDAB”的一个后缀,这个后缀正在与”ABCDAB”进行匹配,

  那么所可能匹配成功的最大长度则是”ABCDAB”删去最后一个字符得到字符串”ABCDA”

  的长度,这个串也是”ABCDAB”的一个前缀,这也就是相当于”ABCDAB”的前缀和后缀在

  匹配,讲到这里,读者应该似乎明白了点什么吧,上述表格似乎有点说法,是吧??那么,

  接着说,既然是一个字符串的前缀和后缀进行匹配,也就是”BCDAB”和”ABCDA”进行匹配,

  即:

      B C D A B       B C D A B       B C D A B  

      ↨               ↨               ↨ 

        A B C D A           A B C D A             A B C D A

      B C D A B         B C D A B

           ↨                 ↨

          A B C D A          A B C D A

    其实以上五个匹配过程也可以这样看:

      B C D A B       C D A B      D A B 

      ↨             ↨        ↨   

        A B C D A           A B C D     A B C 

       A B           B

       ↨         ↨

       A B           A

    你发现了什么??

    好吧,那我说一下我的发现:前缀”ABCDA”一直在与后缀”BCDAB”匹配,如果匹配不成功

  ,那么前缀”ABCDA”的前缀就再与后缀”BCDAB”的后缀继续匹配……所能匹配成功的最大长度

  就是这一步:

      B C D A B

         ↨

           A B C D A

    这一步所得到的最大长度就是1,回到next数组,此时的k值恰好为5,next[5]为1,继续匹

  配将会得到:

      B C D A B

            ↨

            A B C D A

    此时,k为6,next[k]为2!!

    讲到这里,不知道读者是否明白??我已经尽力让读者明白了,如果仍不明白,看来我的表

  述能力存在缺陷,有待提高……

    总结起来,就是上述表格所描述的那样,T的前k-1位的后缀和前缀一直在匹配。

    希望读者能够好好理解一下上述过程,如果实在不理解,可以留言抑或参考推荐的博文。

    OK,下一步用代码求解next数组。

      

 1   void getNext(const string& T) {
 2   
 3     next[0] = -1;
 4     int i = 0;
 5     int j = -1;
 6   
 7     while(T[i] != ‘\0’) {
 8       if(j == -1 || T[i] == T[j]) {    //    这个if应该难不倒读者
 9         ++ i;
10         ++ j;
11         next[i] = j;
12       } else {                    //    关键在这里,如果不相等,那么j就
13         j = next[j];            //    必须回退
14       }
15     }
16   
17   }

     到了这里,至于代码为什么要这么写需要读者自己独立思考一下了……

    至于时间复杂度为什么是O(n+m)就不给予证明了,推荐的July博文有明确证明。

    注:n为S长度,m为T长度

    下面再给出next数组的优化。

    读者可能会问next数组为什么需要优化??在这里举出一个例子进行说明:

      假设母串S为

        aaabaaaab

      模板串T为

        aaaab

      当匹配到这里的时候:

        a a a b a a a a b

              ↨

          a a a a b

    按照上述next数组保存的结果进行匹配,可以发现S[3] != T[3],那么下一步就需要根据

  next[3] = 2进行匹配,然后再根据next[2] = 1进行匹配……直到匹配到T的首字符仍然匹配不

  成功为止。那么在求解next数组的时候就可以预判一下,使得在上述匹配不成功的时候直接

  滑向首字符,省去不必要的匹配过程。也就是说在S[i] == T[j] 时,当S[i + 1] == T[j + 1] 时,

  不需要再和T[j]相比较,而是与T[next[j]]进行比较。那么,next数组求解可以改为下述代码:

    

 1   void getNextval(const string& T) {
 2   
 3     next[0] = -1;
 4     int i = 0;
 5     int j = -1;
 6   
 7     while(T[i] != ‘\0’) {
 8       if(j == -1 || T[i] == T[j]) {
 9         ++ i;
10         ++ j;
11         //    修改部分如下
12         if(T[i] != T[j])
13           next[i] = j;
14         else next[i] = next[j];
15       } else {
16         j = next[j];
17       }
18     }
19   
20   }

    ok,整理一下KMP完整代码:

 1   void getNext(const string& T) {
 2     next[0] = -1;
 3     int i = 0;
 4     int j = -1;
 5   
 6     while(T[i] != ‘\0’) {
 7       if(j == -1 || T[i] == T[j]) {
 8         ++ i;
 9         ++ j;
10         next[i] = j;
11       } else {
12         j = next[j];
13       }
14     }
15   }
16   
17   void getNextval(const string& T) {
18     next[0] = -1;
19     int i = 0;
20     int j = -1;
21   
22     while(T[i] != ‘\0’) {
23       if(j == -1 || T[i] == T[j]) {
24         ++ i;
25         ++ j;
26   
27         if(T[i] != T[j])
28           next[i] = j;
29         else next[i] = next[j];
30       } else {
31         j = next[j];
32       }
33     }
34   }
35   
36   int KMP_Search(const string& S, const string& T) {
37     int i = 0;
38     int j = -1;
39     int sLen = S.size();
40     int tLen = T.size();
41   
42     while(i < sLen && j < tLen) {
43       if(j == -1 || S[i] == T[j]) {
44         ++ i;
45         ++ j;
46       } else {
47         j = next[j];
48       }
49     }
50   
51     if(j == tLen)
52       return i - j;
53   
54     return -1;
55   }

    ok, kmp算法介绍完毕,下面介绍BM算法~

  三、BM算法

    事实上KMP算法并不是最快的匹配算法,BM算法(1977年,Robert S.Boyer和J

  Strother Moore提出)要比KMP算法快,它的时间复杂度为O(n)(平均性能为O(n),

  但有时也会达到O(n * m),而且书写代码要复杂,不细心的读者很容易写错),BM算法

  采用从右向左比较的方法,同时应用到了两种启发式规则,即坏字符规则 和好后缀规则 ,

  来决定向右跳跃的距离。

    例如:

    第一步:

      A B C D E F G H I

            ↨

      C D E F G F

    然后向前匹配:

      A B C D E F G H I

               ↨

        C D E F G F

    这就是从后往前匹配。

    BM算法定义了两种规则:

    注:为方面书写,S称作母串,T称作模板串

    注:规则读一遍即可,下述图文解释匹配过程可完全解释规则含义。

    注:网页上关于BM算法的规则说明原理都是一样的,只不过是表述不同。

      A B C D E F G H I

         ↨

        C D E F E F

    如上图所示,蓝色字符串“EF”就是好后缀,黄色字符“D”就是坏字符。

    1)坏字符规则

      在BM算法从右向左扫描的过程中,若发现某个字符x不匹配,则按如下两种情况讨论:

      I.如果字符x在T中没有出现,那么从字符x开始的m个文本显然不可能与S在此处的字符

    匹配成功,直接全部跳过该区域即可。

      II.如果x在T中出现,选择最右该字符进行对齐。

    2)好后缀规则

      若发现某个字符不匹配的同时,已有部分字符匹配成功,则按如下两种情况讨论:

      I.如果在T中其他位置存在与好后缀相同的子串,选择最边右的子串,将S左移使该子

    串与好后缀对齐(相当于T右移)。

      II.如果在T中任何位置不存在与好后缀相同的字串,查找是T中否存在某一前缀与好后

    缀相匹配,如果有选择最长前缀与S对齐,相当于S左移或者T右移;如果不存在,那么直

    接跳过该后缀,T的首字符与S好后缀的下一字符对齐。

    坏字符规则图文解释:

    (1)

      A  B  C  D  E  F  G H

         ↨

        H  I   J  K

      不匹配,直接跳过,得到:

      A  B  C  D  E  F  G  H

             ↨

            H  I   J  K

    (2)

      A B C D E F G

         ↨

      A D D F

      字符"D"在T中存在,那么得到:

      A  B C D E F G

          ↨

        A D D F

    好后缀规则图文解释:

     (1)

      A B C D E F G H I J K

           ↨

        A E F A E F

      字符"A"与"D"不匹配,好后缀"EF"在T中存在,那么得到:

      A B C D E F G H I J K

               ↨

           A E F A E F

    (2)

      A B C D E F G H I J K L

         ↨

      F G F A E F G

      T中存在前缀“FG”与后缀“FG”相匹配,那么得到:

      A B C D E F G H I J K L

                 ↨

            F G F A E F G

    如果是这样:

       A B C D E F G H I J K L M N

         ↨

       F A F A E F G

      不存在前缀与任一后缀匹配,那么得到:

      A B C D E F G H I J K L M N

                    ↨

               F A F A E F G

    ok,原理说明完毕,下面就是代码求解:

    注:网上诸多作者均给出了详细求解代码,可是求解代码比较晦涩难懂,没有比较好的注释

  以帮助读者完全理解,所以在此根据编者自己的理解,给出了晦涩代码段的相关注释。

    先给出BM算法的匹配代码:

    注:bmG表示好后缀数组,bmB表示坏字符数组

 1   int BM(const string& S, const string& T, int bmG[], int bmB[]) {
 2   
 3     int sLen = S.size() - T.size();
 4     int tLen = T.size();
 5     int i = 0;
 6   
 7     get_bmB(T, bmB);
 8     get_bmG(T, bmG);
 9   
10     while(i <= sLen) {
11       int j = tLen - 1;
12       //    出现不匹配字符或者匹配成功循环结束
13       for( ; j > -1 && S[i + j] == T[j]; --j) ;
14   
15       //    匹配成功
16       if(j == -1)
17         return i;
18   
19       //    选择好后缀与坏字符中移位最大者,tLen - 1 - j表示的是该字符距字符串尾部的距离,bmB[S[i + j]]表示的是该字符
20       //    出现在T中的最右位置距字符串尾部的距离。
21       i += max(bmG[j], bmB[S[i + j]] - (tLen - 1 - j));
22     }
23   
24     return -1;
25   }

    那么,下面就需要求解bmG数组和bmB数组了,由于求解bmG数组比较麻烦,所以先给出

  bmB数组的求解代码:

 1   void get_bmB(const string& T, int bmB[]) {
 2   
 3     int tLen = T.size();
 4   
 5     //    MAXSIZE 表示字符种类数目
 6     //    坏字符不存在T中时,直接后移此片段
 7     for(int i = 0; i < MAXSIZE; ++ i) {
 8       bmB[i] = tlen;
 9     }
10   
11     //    坏字符存在T中时,选择T中最右字符存在的位置
12     for(int i = 0; i < tLen; ++i) {
13       bmB[T[i]] = tLen - 1 - i;
14     }
15   
16   }

    代码很简单,读者稍加理解应该没问题。

    OK,下面给出bmG数组的求解代码:

 1   //    bmGLength[i]代表的是T在以该字符为后缀的字符串与T的后缀所能匹配的最大长度
 2   int bmGlength[N];
 3   
 4   void get_bmG(const string& T, int bmG[]) {
 5   
 6     //    求解好后缀数组那么必先得到匹配不成功处好后缀的长度是多少
 7     get_bmGLength(T, bmGLength);
 8   
 9     //    那么按照好后缀的原理,先默认选择匹配不成功之处不存在好后缀的情况
10     int tLen = T.size();
11     for(int i = 0; i < tLen; ++ i) {
12       bmG[i] = tLen;
13     }
14   
15   
16     //    注:以下代码中i之所以 < tLen - 1是因为下标tLen - 1处不存在好后缀,只有坏字符
17   
18     //    然后是选择匹配不成功之处T中存在包含该字符的前缀与好后缀相匹配的情况
19     int j = 0;
20     for(int i = tLen - 2; i >= 0; -- i) {
21       //    若存在前缀与后缀相匹配,那么此处所保存的数值必然是i + 1
22       if(bmGLength[i] == i + 1) {
23         for(; j < tLen - 1 - i; ++ j) {
24           //    还没有变化过时方可赋值
25           if(bmG[j] == tLen)
26             bmG[j] = tLen - 1 - i;
27         }
28       }
29     }
30   
31     //    最后就是不匹配处T中存在与好后缀相匹配的字符串,选择最右
32     //    字符串
33     for(int i = 0; i < tLen - 1; ++ i) {
34       bmG[tLen - 1 - bmGLenth[i]] = tLen - 1 - i;
35     }
36   
37   }

    那么接着给出bmGLength数组的求解代码:

    注:代码中有很多地方尤其是数组下标中加了相关括号,这是方便读者理解的地方,希望

  读者要稍加注意

 1   void get_bmGLength(const string& T, int bmGLength[]) {
 2     int tLen = T.size();
 3     bmGLength[tLen - 1] = tLen;
 4     for(int i = tLen - 2; i >= 0; -- i) {
 5       int j = i;
 6   
 7       while(j >= 0 && T[j] == T[tLen - 1 - (i - j)])
 8         -- j;
 9   
10       bmGLenth[i] = i - j;
11     }
12   
13   }

    不难发现,此代码的时间复杂度为O(n2),事实上我们可以优化一下,优化成O(n),

  下见代码:

    为方便读者理解,先说一下以下代码的原理:

    如果i所到的最远处cur与pre不等,那么就存在最小循环节在[cur, pre]和[cur, tLen - 1]中,

  这个最小循环节内部(指除去最右字符剩下的字符)的字符必然不是完全匹配(也就是从此

  字符开始与后缀进行匹配,必然匹配不到一个循环节的长度),这些不完全匹配的字符所能

  匹配的最大长度在每个循环节中必然相同;而完全匹配的字符(也就是最右字符)所能匹配

  的最大长度的差值必然是循环节的整数倍。

    不知道这样说对不对,若有不对之处还请读者指正,若在理还请读者细细品味。

    这个原理也仅是读者通过代码进行理解而得到的,至于原来最先想出此优化代码的程序员已

  不可考,其根本原理也已不可知。

 1   void get_bmGLength(const string& T, int bmGLength[]) {
 2     int tLen = T.size();
 3     bmGLength[tLen - 1] = tLen;
 4   
 5     int cur = tLen - 1;    //    保存当前下标i所到的最远位置
 6     int pre;    //    保存下标i先前的位置
 7   
 8     for(int i = tLen - 2; i >= 0; -- i) {
 9   
10     //    i > cur 是因为i必须要曾经遍历到过下标cur方可
11     //    (tLen - 1 - pre)表示先前i到T尾字符的长度
12     //    bmGLength[i + (tLen - 1 - pre)] < i - cur这个条件为什么要加上,
13     //    我也不明白,不过去掉这个条件,按照原理是成立的,加上也没有错,给出一个题目链接,读者可以测试一下编者说的是否正确
14     //    http://acm.hdu.edu.cn/showproblem.php?pid=1711
15
16     /*    网上源代码:
17       if(i > cur && bmGLength[i + (tLen - 1 - pre)] < i - cur) {
18         bmGLength[i] = bmGLength[i + (tLen - 1 - pre)];
19         continue;
20       }
21     */
22     //    原理代码:
23       if(i > cur) {
24         bmGLength[i] = bmGLength[i + (tLen - 1 - pre)];
25         continue;
26       }
27       //    i所到的最远的位置必然是i最小时
28       cur = min(cur, i);
29       pre = i;
30   
31       while(cur >= 0 && T[cur] == T[tLen - 1 - (pre - cur)])
32         -- cur;
33   
34       bmGLenth[i] = pre - cur;
35     }
36   
37   }
38   

    OK,讲解完毕,下面给出BM完整代码:

 1   void get_bmB(const string& T, int bmB[]) {
 2     int tLen = T.size();
 3     for(int i = 0; i < MAXSIZE; ++ i) {
 4       bmB[i] = tlen;
 5     }
 6
 7     for(int i = 0; i < tLen; ++i) {
 8       bmB[T[i]] = tLen - 1 - i;
 9     }
10   }
11
12   void get_bmGLength(const string& T, int bmGLength[]) {
13     int tLen = T.size();
14     bmGLength[tLen - 1] = tLen;
15     for(int i = tLen - 2; i >= 0; -- i) {
16       int j = i;
17       while(j >= 0 && T[j] == T[tLen - 1 - (i - j)])
18         -- j;
19   
20       bmGLenth[i] = i - j;
21     }
22   }
23   
24   void get_bmGLength(const string& T, int bmGLength[]) {
25     int tLen = T.size();
26     bmGLength[tLen - 1] = tLen;
27   
28     int cur = tLen - 1;
29     int pre;
30   
31     for(int i = tLen - 2; i >= 0; -- i) {
32     /*    网上源代码:
33       if(i > cur && bmGLength[i + (tLen - 1 - pre)] < i - cur) {
34         bmGLength[i] = bmGLength[i + (tLen - 1 - pre)];
35         continue;
36       }
37     */
38     //    原理代码:
39       if(i > cur) {
40         bmGLength[i] = bmGLength[i + (tLen - 1 - pre)];
41         continue;
42       }
43   
44       cur = min(cur, i);
45       pre = i;
46       while(cur >= 0 && T[cur] == T[tLen - 1 - (pre - cur)])
47         -- cur;
48       bmGLenth[i] = pre - cur;
49     }
50   }
51   
52   int bmGlength[N];
53   
54   void get_bmG(const string& T, int bmG[]) {
55   
56     get_bmGLength(T, bmGLength);
57   
58     int tLen = T.size();
59     for(int i = 0; i < tLen; ++ i) {
60       bmG[i] = tLen;
61     }
62   
63     int j = 0;
64     for(int i = tLen - 2; i >= 0; -- i) {
65       if(bmGLength[i] == i + 1) {
66         for(; j < tLen - 1 - i; ++ j) {
67           if(bmG[j] == tLen)
68             bmG[j] = tLen - 1 - i;
69         }
70       }
71     }
72   
73     for(int i = 0; i < tLen - 1; ++ i) {
74       bmG[tLen - 1 - bmGLenth[i]] = tLen - 1 - i;
75     }
76   }
77
78   int BM(const string& S, const string& T, int bmG[], int bmB[]) {
79
80     get_bmB(T, bmB);
81     get_bmG(T, bmG);
82   
83     int sLen = S.size() - T.size();
84     int tLen = T.size();
85     int i = 0;
86   
87     while(i <= sLen) {
88       int j = tLen - 1;
89       for(; j > -1 && S[i + j] == T[j]; --j) ;
90
91       if(j == -1)
92         return i;
93   
94       i += max(bmG[j], bmB[S[i + j]] - (tLen - 1 - j));
95     }
96     return -1;
97   }

    ok,BM算法介绍完毕,下面介绍Sundy算法,最简单的算法。

  四、Sunday算法

    Sunday算法是Daniel M.Sunday于1990年提出的字符串模式匹配。相对比较KMP和BM

  算法而言,简单了许多。

    原理与BM算法相仿,有点像其删减版,所以其时间复杂度和BM算法差不多,平均性能的

  时间复杂度也为O(n),最差情况的时间复杂度为O(n * m),但是要容易理解。

    匹配原理:从前往后匹配,如果遇到不匹配情况判断母串S参与匹配的最后一位的下一位字符

  ,如果该字符出现在模板串T中,选择最右出现的位置进 行对齐;否则直接跳过该匹配区域。

    原理看着都这么繁琐,而且难懂,还是给读者上图吧:

    母串S:

      S  E  A  R  C  H  S  U  B  S  T  R  I  N  G

    模板串T:

      S  U  B  S  T  R  I  N  G

    开始匹配:

      S  E  A  R  C  H  S  U  B  S  T  R  I  N  G

      ↨

      S  U  B  S  T  R  I  N  G

    继续下一字符匹配:

      S  E  A  R  C  H  S  U  B  S  T  R  I  N  G

        ↨

      S  U  B  S  T  R  I  N  G

    出现不匹配情况,查找母串参与匹配的最后一位字符的下一字符,上图中S中最后一位参与

  匹配的字符是颜色为蓝色的字符’B’,其下一字符为’S’,在T中,字符’S’出现两次,按照原理,

  选择最右位置出现的’S’进行对齐,那么可以得到:

      S  E  A  R  C  H  S  U  B  S  T  R  I  N  G

               ↨

                 S  U  B  S  T  R  I  N  G

    直接跳过大片区域。

    假设母串S为:

      S  E  A  R  C  H  S  U  B  Z  T  R  I  N  G

    那么当匹配到上述情况时,字符’Z’在T中没有出现,那么就可以得到下面的情况:

      S  E  A  R  C  H  S  U  B  Z  T  R  I  N  G

                     ↨

                           S  U  B  S  T  R  I  N  G

    跳过的区域很大吧。

    ok,这就是其原理的两种情况,很简单吧,下面给出代码解释:

    注:S表示母串,T表示模板串

 1   int moveLength[MAXSIZE];    //    匹配不成功时的移动步长,默认初始化
 2   
 3   int Sunday(const string& S, const string &T) {
 4     getMoveLength(T);
 5   
 6     int tLen = T.size();
 7     int sLen = S.size();
 8     int i = 0;    //    S遍历下标
 9     while(i < sLen) {
10       int j = 0;
11       //    符合条件下标就继续右移
12       for(  ; j < tLen && i + j < sLen && S[i + j] == T[j]; ++ j) ;
13       //    遍历结束,判断遍历情况
14       if(j >= tLen) return i;
15       //    查找不成功,那么S下标右移
16       if(i + tLen >= sLen)
17       return -1;
18       i += moveLength[S[i + tLen]];
19     }
20   
21     return -1;
22   }

    匹配过程很简单,相信读者很容易理解。

    那么,需要求解moveLength数组,下面给出其求解代码:

 1   int MAXSIZE = 256;    //    字符串种类数,视情况而定
 2
 3   void getMoveLength(const string &T) {
 4     int tLen = T.size();
 5     //    默认S中的任何字符均不出现在T中,那么每次移动的距离为T的长度 + 1
 6     for(int i = 0; i < MAXSIZE; ++ i)
 7       moveLength[i] = tLen + 1;
 8   
 9     //    查找能够出现在T中的字符,若一个字符出现多次,选择最右位置的字符,所以T的下标遍历从0开始
10     for(int i = 0; T[i]; ++ i)
11       moveLength[T[i]] = tLen - i;
12   }
13   

    ok,代码解释完毕,非常简单。

    下面给出完整代码:

 1   int MAXSIZE = 256;
 2   int moveLength[MAXSIZE];
 3
 4   void getMoveLength(const string &T) {
 5     int tLen = T.size();
 6     for(int i = 0; i < MAXSIZE; ++ i)
 7       moveLength[i] = tLen + 1;
 8   
 9     for(int i = 0; T[i]; ++ i)
10       moveLength[T[i]] = tLen - i;
11   }
12   
13   int Sunday(const string& S, const string &T) {
14     getMoveLength(T);
15   
16     int tLen = T.size();
17     int sLen = S.size();
18     int i = 0;
19     while(i < sLen) {
20       int j = 0;
21       for(  ; j < tLen && i + j < sLen && S[i + j] == T[j]; ++ j) ;
22   
23       if(j >= tLen) return i;
24       if(i + tLen > sLen)
25         return -1;
26       i += moveLength[S[i + tLen]];
27     }
28   
29     return -1;
30   }

  BF、KMP、BM、Sunday算法均介绍完毕,若读者有不明之处抑或博文有误之类,请尽情留言,编者看到后会及时回复及修改博文。

  希望读者理解四种算法之后能够自己独立手写其核心代码,对读者加深印象以及其核心原理很有裨益。

  最后再次列举推荐博文以及相关书籍(当然也曾读到过一些博文以及书籍,从编者角度来说不易理解,便没有推荐,还望见谅):

    July博文(再次推荐)  http://blog.csdn.net/v_july_v/article/details/7041827

    算法导论(第三版),机械工业出版社,Thomas H. Cormen ... 著,殷建平...译

  暂时推荐上面比较稀少的博文与书籍,若有读者能够推荐给编者比较好的博文及书籍抑或日后看到,定会添加于上。

                                            2014年11月5日

BF、KMP、BM、Sunday算法讲解相关推荐

  1. BF,KMP,BM三种字符串匹配算法性能比较

    三种最基本的字符串匹配算法是BF,KMP以及BM,BF算法是最简单直接的匹配算法,就是逐个比较,一旦匹配不上,就往后移动一位,继续比较,所以比较次数很都. 关于KMP和BM的详细介绍可以参考下面的两个 ...

  2. sunday 算法python实现

    同为字符串查找的算法, Sunday真的比KMP简单太多了. 理解也很简单: 下面的文字可能比较难理解, 可以参考这个博主的博客理解 [字符串匹配--Sunday算法]https://blog.csd ...

  3. Sunday算法详解

    一:背景 Sunday算法是Daniel M.Sunday于1990年提出的字符串模式匹配.其效率在匹配随机的字符串时比其他匹配算法还要更快.Sunday算法的实现可比KMP,BM的实现容易太多. 二 ...

  4. 数据结构与算法之美笔记——基础篇(下):图、字符串匹配算法(BF 算法和 RK 算法、BM 算法和 KMP 算法 、Trie 树和 AC 自动机)

    图 如何存储微博.微信等社交网络中的好友关系?图.实际上,涉及图的算法有很多,也非常复杂,比如图的搜索.最短路径.最小生成树.二分图等等.我们今天聚焦在图存储这一方面,后面会分好几节来依次讲解图相关的 ...

  5. a - 数据结构实验之串一:kmp简单应用_串的两种模式匹配方式(BF/KMP算法)

    串的两种模式匹配方式(BF/KMP算法) 前言 串,又称作字符串,它是由0个或者多个字符所组成的有限序列,串同样可以采用顺序存储和链式存储两种方式进行存储,在主串中查找定位子串问题(模式匹配)是串中最 ...

  6. Sunday算法介绍及Java实现

    前言 最初想写这篇文章的原因是在LeetCode上看到了一道实现strStr函数的题: 实现 strStr() 函数. 给定一个 haystack 字符串和一个 needle 字符串,在 haysta ...

  7. c语言指opt算法实现,Sunday算法c语言版实现

    一.BF算法 BF算法是普通的模式匹配算法,其基本思想就是将目标串的第一个字符与模式串的第一个字符进行匹配.若相等,则继续比较第二个字符:若不相等,则比较目标串的第二个字符和模式串的第一个字符.依次比 ...

  8. 【算法】从后向前的字符串匹配算法——BMH算法+sunday算法

    前言 KMP算法将从前向后的字符串匹配的效率发挥到了极致,所以想要进一步提升,只能打破思维定式,找到一条与众不同的路.所以从后往前的字符串匹配算法就应运而生.它可以更为高效的快速移动字符串,但是在最坏 ...

  9. sunday算法特征码_Sunday算法介绍及Java实现

    前言 最初想写这篇文章的原因是在LeetCode上看到了一道实现strStr函数的题: 实现 strStr() 函数. 给定一个 haystack 字符串和一个 needle 字符串,在 haysta ...

最新文章

  1. Maze Problem(求最短距离)BFS
  2. leetcode -- 303. 区域和检索 - 数组不可变
  3. mysql 层_mysql三层体系
  4. iOS UI-常用控件
  5. PickerView的简单介绍
  6. .gliffy文件怎么打开和gliffy的免费试用两周
  7. RESTfull API简单项目的快速搭建
  8. geany中正确运行python方法
  9. Python爬虫实战之五:requests-re多页爬取链家成都地区租房市场信息
  10. Ospf Forwarding address路由选路的影响
  11. Spark2.1.0 + CarbonData1.0.0集群模式部署及使用入门
  12. 如何将文件FLAC格式快速转换为MP3格式
  13. stm32f103r6最小系统原理图_PCB stm32的最小系统原理图 基于STM32F103VBT6的最小系统板 - 下载 - 搜珍网...
  14. MTU问题,为何抓包到1514
  15. Centos7.4在vmware6.5下基于nfs和dd实现虚机的备份恢复
  16. Nginx的二进制安装教程
  17. C++中类(class)和结构(struct)的区别
  18. 小学生也能看懂的海伦公式推导
  19. 【操作系统概念-作业1】Introduction
  20. Javascript模块化编程,requireJs

热门文章

  1. 百度paddlepaddle七天打卡之青你实战
  2. 我和谷歌共成长-资深安卓开发的转型之路
  3. Could NOT find Vulkan (missing: Vulkan_LIBRARY Vulkan_INCLUDE_DIR) | 解压安装
  4. Android在中国的发展及就业前景解析
  5. Spring Boot 学习笔记 8 : Elasticsearch
  6. 永恒之蓝勒索漏洞复现
  7. python实现数据结构--线性表
  8. android im---weichat
  9. 探究App推广之路:流量思维永不死 ☞ iphone中App store上架优化建议
  10. 三相短路电流计算机算法的原理什么,三相短路电流的计算机算法