1、个人觉得这篇文章是网上的介绍有关 KMP 算法更让人容易理解的文章了,确实说得很“详细” ,耐心地把它看完肯定会有所收获的,另外有关模式函数值 nexti确实有很多版本啊,在另外一些面向对象的算法描述书中也有失效函数 f(j)的说法,其实是一个意思,即nextj=f(j-1)+1,不过还是 nextj这种表示法好理解啊:KMP 字符串模式匹配详解KMP 字符串模式匹配通俗点说就是一种在一个字符串中定位另一个串的高效算法。简单匹配算法的时间复杂度为 O(m*n);KMP 匹配算法。可以证明它的时间复杂度为 O(m+n).。一.简单匹配算法先来看一个简单匹配算法的函数:int Index_BF (
2、 char S , char T , int pos ) /* 若串 S 中从第 pos(S 的下标 0pos S0 != S1,S1 != S2,所以 S1 != T0,S2 != T0. 还是从理论上间接比较了。有人疑问又来了,你分析的是不是特殊轻况啊。假设 S 不变,在 S 中搜索 T=“abaabd”呢?答:这种情况,当比较到 S2和 T2时,发现不等,就去看 next2的值,next2=-1,意思是 S2已经和 T0 间接比较过了,不相等,接下来去比较 S3和 T0吧。假设 S 不变,在 S 中搜索 T=“abbabd”呢?答:这种情况当比较到 S2和 T2时,发现不等,就去看 ne
3、xt2的值,next2=0,意思是 S2已经和 T2比较过了,不相等,接下来去比较 S2和 T0吧。假设 S=”abaabcabdabba”在 S 中搜索 T=“abaabd”呢?答:这种情况当比较到 S5和 T5时,发现不等,就去看 next5的值,next5=2,意思是前面的比较过了,其中,S5的前面有两个字符和 T 的开始两个相等,接下来去比较 S5和 T2吧。总之,有了串的 next 值,一切搞定。那么,怎么求串的模式函数值 nextn呢?(本文中next 值、模式函数值、模式值是一个意思。 )三. 怎么求串的模式值 nextn定义:(1 ) next0= -1 意义:任何串的第一个字
4、符的模式值规定为-1 。(2 ) nextj= -1 意义:模式串 T 中下标为 j 的字符,如果与首字符相同,且 j 的前面的 1k 个字符与开头的 1k个字符不等(或者相等但 Tk=Tj) (1k0 但 k #include int KMP(const char *Text,const char* Pattern) /const 表示函数内部不会改变这个参数的值。 if( !Text|!Pattern| Pattern0=/0 | Text0=/0 )/ return -1;/空指针或空串,返回-1。int len=0; const char * c=Pattern; while(*c+!
5、=/0)/移动指针比移动下标快。 +len;/字符串长度。 int *next=new intlen+1;get_nextval(Pattern,next);/求 Pattern 的 next 函数值int index=0,i=0,j=0; while(Texti!=/0 / 继续比较后继字符+j; else index += j-nextj; if(nextj!=-1) j=nextj;/ 模式串向右移动else j=0; +i; /whiledelete next;if(Patternj=/0) return index;/ 匹配成功else return -1; int main()/a
6、bCabCad char* text=“bababCabCadcaabcaababcbaaaabaaacababcaabc“; char*pattern=“adCadCad“; /getNext(pattern,n); /get_nextval(pattern,n); coutKMP(text,pattern)endl; return 0; 五其他表示模式值的方法上面那种串的模式值表示方法是最优秀的表示方法,从串的模式值我们可以得到很多信息,以下称为第一种表示方法。第二种表示方法,虽然也定义 next0= -1,但后面绝不会出现-1,除了 next0,其他模式值 nextj=k(0kj)的意义
7、可以简单看成是:下标为 j 的字符的前面最多 k 个字符与开始的 k 个字符相同,这里并不要求 Tj != Tk。其实 next0也可以定义为 0(后面给出的求串的模式值的函数和串的模式匹配的函数,是 next0=0 的) ,这样,nextj=k(0kj)的意义都可以简单看成是:下标为 j 的字符的前面最多 k 个字符与开始的 k个字符相同。第三种表示方法是第一种表示方法的变形,即按第一种方法得到的模式值,每个值分别加 1,就得到第三种表示方法。第三种表示方法,我是从论坛上看到的,没看到详细解释,我估计是为那些这样的编程语言准备的:数组的下标从 1 开始而不是 0。下面给出几种方法的例子:表一
8、。下标 0 1 2 3 4 5 6 7 8T a b a b c a a b c(1) next -1 0 -1 0 2 -1 1 0 2(2) next -1 0 0 1 2 0 1 1 2 (3) next 0 1 0 1 3 0 2 1 3 第三种表示方法,在我看来,意义不是那么明了,不再讨论。表二。 下标 0 1 2 3 4T a b c A c(1)next -1 0 0 -1 1(2)next -1 0 0 0 1 表三。下标 0 1 2 3 4 5 6 7T a d C a d C a d(1)next -1 0 0 -1 0 0 -1 0(2)next -1 0 0 0 1 2
9、 3 4 对比串的模式值第一种表示方法和第二种表示方法,看表一:第一种表示方法 next2= -1,表示 T2=T0,且 T2-1 !=T0 第二种表示方法 next2= 0,表示 T2-1 !=T0,但并不管 T0 和 T2相不相等。第一种表示方法 next3= 0,表示虽然 T2=T0,但 T1 =T3 第二种表示方法 next3= 1,表示 T2 =T0,他并不管 T1 和 T3相不相等。第一种表示方法 next5= -1,表示 T5=T0,且 T4 !=T0,T3T4 !=T0T1,T2T3T4 !=T0T1T2 第二种表示方法 next5= 0,表示 T4 !=T0,T3T4 !=T
10、0T1 ,T2T3T4 !=T0T1T2,但并不管 T0 和 T5相不相等。换句话说:就算 T5=x,或 T5=y,T5=9,也有 next5= 0 。从这里我们可以看到:串的模式值第一种表示方法能表示更多的信息,第二种表示方法更单纯,不容易搞错。当然,用第一种表示方法写出的模式匹配函数效率更高。比如说,在串 S=“adCadCBdadCadCad 9876543”中匹配串 T=“adCadCad”, 用第一种表示方法写出的模式匹配函数,当比较到 S6 != T6 时,取 next6= -1(表三) ,它可以表示这样许多信息: S3S4S5=T3T4T5=T0T1T2,而 S6 != T6,T
11、6=T3=T0,所以 S6 != T0,接下来比较 S7和 T0吧。如果用第二种表示方法写出的模式匹配函数, 当比较到 S6 != T6 时,取 next6= 3(表三),它只能表示:S3S4S5= T3T4T5=T0T1T2,但不能确定 T6与 T3相不相等,所以,接下来比较 S6和 T3;又不相等,取 next3= 0,它表示S3S4S5= T0T1T2,但不会确定 T3与 T0相不相等,即 S6和 T0 相不相等,所以接下来比较 S6和 T0,确定它们不相等,然后才会比较 S7和 T0。是不是比用第一种表示方法写出的模式匹配函数多绕了几个弯。为什么,在讲明第一种表示方法后,还要讲没有第一
12、种表示方法好的第二种表示方法?原因是:最开始,我看严蔚敏的一个讲座,她给出的模式值表示方法是我这里的第二种表示方法,如图:她说:“next 函数值的含义是:当出现 Si !=Tj时,下一次的比较应该在 Si和 Tnextj 之间进行。 ”虽简洁,但不明了,反复几遍也没明白为什么。而她给出的算法求出的模式值是我这里说的第一种表示方法 next 值,就是前面的 get_nextval()函数。匹配算法也是有瑕疵的。于是我在这里发帖说她错了:http:/ nextj=k(0kj) ,而算法中 nextj=k(-1kj)) 。凭良心说:光看这个讲座,我就对这个教受十分敬佩,不仅讲课讲得好,声音悦耳,而
13、且这门课讲得层次分明,恰到好处。在 KMP 这个问题上出了点小差错,可能是编书的时候,在这本书上抄下了例子,在那本书上抄下了算法,结果不怎么对得上号。因为我没找到原书,而据有的网友说,书上已不是这样,也许吧。说起来,教授们研究的问题比这个高深不知多少倍,哪有时间推演这个小算法呢。总之,瑕不掩玉。书归正传,下面给出我写的求第二种表示方法表示的模式值的函数,为了从 S 的任何位置开始匹配 T, “当出现 Si !=Tj时,下一次的比较应该在 Si和 Tnextj 之间进行。 ” 定义 next0=0 。void myget_nextval(const char *T, int next) / 求模
14、式串 T 的 next 函数值(第二种表示方法)并存入数组 next。 int j = 1, k = 0; next0 = 0; while ( Tj != /0 ) if(Tj = Tk) nextj = k; +j; +k; else if(Tj != T0) nextj = k; +j; k=0; else nextj = k; +j; k=1; /while for(int i=0;ij;i+) coutnexti; coutendl; / myget_nextval 下面是模式值使用第二种表示方法的匹配函数(next0=0)int my_KMP(char *S, char *T, i
15、nt pos) int i = pos, j = 0;/pos(S 的下标 0posStrLength(S) while ( Si != /0 +j; / 继续比较后继字符 else / a b a b c a a b c / 0 0 0 1 2 0 1 1 2 /-1 0 -1 0 2 -1 1 0 2 i+; j = nextj; /*当出现 Si !=Tj时,下一次的比较应该在 Si和 Tnextj 之间进行。要求 next0=0。在这两个简单示范函数间使用全局数组 next传值。*/ /while if ( Tj = /0 ) return (i-j); / 匹配成功else retu
16、rn -1; / my_KMP 六后话-KMP 的历史这段话是抄的 Cook 于 1970 年证明的一个理论得到,任何一个可以使用被称为下推自动机的计算机抽象模型来解决的问题,也可以使用一个实际的计算机(更精确的说,使用一个随机存取机)在与问题规模对应的时间内解决。特别地,这个理论暗示存在着一个算法可以在大约 m+n的时间内解决模式匹配问题,这里 m 和 n 分别是存储文本和模式串数组的最大索引。Knuth 和 Pratt 努力地重建了 Cook 的证明,由此创建了这个模式匹配算法。大概是同一时间,Morris 在考虑设计一个文本编辑器的实际问题的过程中创建了差不多是同样的算法。这里可以看到并不是所有的算法都是“灵光一现”中被发现的,而理论化的计算机科学确实在一些时候会应用到实际的应用中。