KMP算法是一种改进的模式匹配算法,相比于朴素的模式匹配算法效率更高。下面讲解KMP算法的基本思想与实现。

  先来看一下朴素模式匹配算法的基本思想与实现。

  朴素模式匹配算法的基本思想是匹配过程中如果该位置相等,继续匹配各自的下一位,直至匹配完成,或者出现一位不匹配,如果该位置不相等,主串的匹配位置返回上次开始匹配位置的下一位,副串的匹配位置再次从头开始。

实现程序如下:

  主串s,副串t,如果存在,返回t在s中第一次出现的位置,否则返回-1。

 

 1 int Index(char *s,char *t){
 2     int ls=strlen(s),lt=strlen(t);
 3     int i=0;//主串匹配位置 
 4     int j=0;//副串匹配位置 
 5     while(i < ls && j < lt){
 6         if(s[i] == t[j]){
 7             i++;
 8             j++;
 9         }
10         else{
11             i=i-j+1;
12             j=0;
13         }
14     }
15     if(j >= lt)//副串匹配完成
16         return i-j;
17     else
18         return -1; 
19 }

  可以设想,最糟糕的情况是每次的匹配的不成功均发生在了最后一位匹配,那么它的时间就主要浪费在了主串回溯的过程,比如00000001(7个0)和001,就有5次回溯是不必要的,它浪费的时间是随着匹配成功之间的0的个数增多而增多,自然就想到没有什么办法去解决这个问题呢?

  下面就轮到KMP算法登场了。

  为了解决每次主串匹配不成功不用回溯或者说尽可能不回溯的问题,就想到了用不用回溯和副串有什么关系。下面看一个例子

abcdefgh

abcz

按照之前的算法,会有如下的匹配过程:

①  

abcdefgh

abcz

②  

abcdefgh

  abcz

③  

abcdefgh

    abcz

④  

abcdefgh

      abcz

⑤  

abcdefgh

        abcz

  拿1和2来说,已知副串中ab第一位a和第二位b不相同,而副串和主串中前两位相同,可以知道副串中的第一位a和主串中的第二位b(也就是回溯的位置)肯定不相同,换句话说,这次回溯是可以避免的,同样的道理,匹配过程3也是可以避免的。再来研究一下这个规律,主串和副串的匹配过程中,要想不回溯,i的值是不能减少的,而j的值有和副串当前匹配的子串本身的重复有关系。

  那么这个关系是什么呢?结论是跟这个子串的前后缀的相似度有关。

  那么怎么计算一个子串的前后缀的相似度呢?

  我们用next[j]数组来表示j下次应该返回的位置,函数定义如下:

  next[j]=   0,当j=0

      max({k|0<k<=j,且p0…pk=p(j-k)…pj},当此集合不为空

      1,其他

算法实现如下:

  副串t,用next数组存储结果。

 1 void get_next(char t[],int next[])
 2 {
 3     int l=strlen(t);
 4     int i=0;//副串匹配位置 
 5     int j=-1;//next数组位置指针,初始化为-1,表示回溯边界 
 6     next[0]=-1;//0表示长度为0的子串的前后缀相似度为-1,也表示回溯边界
 7     while(i < l){
 8         if(j == -1 || t[i] == t[j]){
 9             i++;
10             j++;
11             next[i]=j;
12         }
13         else
14             j=next[j];
15     } 
16 }

下面将制作好的next数组应用到KMP算法中。

代码如下:

 1 int Index_KMP(char *s,char *t){
 2     int ls=strlen(s),lt=strlen(t);
 3     int i=0;//主串起始匹配位置 
 4     int j=0;//副串起始匹配位置
 5     int next[maxn]={0};
 6     get_next(t,next);
 7     while(i < ls && j < lt){
 8         if(j == -1 || s[i] == t[j]){//增加j==-1表示回溯到了边界的情况 
 9             i++;
10             j++;
11         }
12         else{ 
13             j=next[j];//j返回到合适的位置,而i不用改变 
14         }
15     }
16     if(j >= lt)//匹配完成
17         return i-j;
18     else
19         return -1; 
20 }

  可以看到KMP算法相较朴素的模式匹配算法,多了制作next数组,多了一个判断条件,就成功的避免了主串不必要的回溯,节省了时间。下面给出几道练习题目,巩固知识。

  1.简单的模板题HDU 1711 Number Sequence

  https://www.cnblogs.com/wenzhixin/p/7345115.html

  2.需要一点思维转换HDU 2203 亲和串

  https://www.cnblogs.com/wenzhixin/p/7344076.html

  3.加深印象HDU 3746 Cyclic Nacklace

  https://www.cnblogs.com/wenzhixin/p/7345115.html

  其他练习参考右侧分类中的KMP。

 

版权声明:本文为wenzhixin原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/wenzhixin/p/9346918.html