前两天数据结构课上讲了字符串匹配,老师提了下KMP算法,但是没讲,让有兴趣的同学自己研究。。以前就听说过KMP挺强大也挺难理解的,出于兴趣,下课回寝翻开那本9快钱买的盗版算法导论自己看了一下午,终于理解了KMP的精华,发现它的原理并不是很复杂,只不过书上那些术语太专业了。。让人一时搞不懂什么含义,从而降低了KMP的可理解性。。。所以下面就用比较朴素的语句描述下我对KMP的理解。。。
KMP是一个高效的算法,时间复杂度O(n+m)级,是计算机科学界神一级的人物高德纳和另外两个人共同提出的,它是对有限状态机匹配算法的一种改进,说道有限状态机不得不说下序列检测器。。。大一曾经学过一门叫电子技术基础的一门课,里面关于时序逻辑电路设计中有一个叫做序列检测器的东西,这个序列检测器就是检测输入信号中是否包含一个特定的串的一个硬件,它就是有限状态自动机的原型。。。没看过KMP之前我自己就利用过序列检测器的匹配原理写了一个匹配字符串的程序,和KMP的本体差不多,不过在状态跳转时没KMP高效。。。。
下面开始进入正题:
下面将主串设为T[n]。。模式串(就是要在T中寻找的那个串)设为P[m];
既然P的长度为m,则P就有m个状态,这些状态分别表示已经匹配了P中的多少个字符。。打个比方——第K个状态代表已经匹配了P中的前K个字符,如果主串中下一个字符T[i]与P[K+1]匹配,那么就进入到K+1状态,代表已经匹配了P中的前K+1个字符。。。
如果在第K个状态时P[K+1]与T[i]不匹配呢,这就需要状态跳转了,找到当前已经匹配部分P[1....K]的最大后缀P[L.....K],并且保证这个后缀是P的前缀(即P[1.......(K-L+1)]==P[L....K]),此时的状态为K-L+1,在此基础上如果T[i]=P[K-L+1+1]则进入状态K-L+1+1(即K-L+2),如果还不相等,则在K-L+1的状态上继续上面的过程,直到和匹配上或状态回到0。
举个例子:设T[n]="ababbba",P[m]="abb";(便于方便,字符串首地址从1开始)
则上面思想执行过程如下:
k表示当前匹配状态,起始状态k=0;
因为T[1]=P[k+1],第一个字符匹配成功,进入下一状态 即k=k+1; (此时k=1)
因为T[2]=P[k+1],第二个字符匹配成功,进入下一状态,即k=k+1 (此时k=2,如果下一个字符还能匹配上,则说明这个序列已经出现了 )
因为T[3]!=P[k+1],第三个字符匹配失败,此时已经匹配了两个字符"ab",在这两个字符的串中寻找是P前缀的最大后缀。(因为"ab"没有后 缀是"abb"的前缀,所以k跳转回了0),P[k+1]=T[3],匹配成功, k=k+1; (此时k=1);
因为T[4]=P[k+1],T[4]与P[k+1]匹配成功,进入下一状态 k=k+1 (此时k=2)
因为T[5]=P[k+1],T[4]与P[k+1]匹配成功,进入下一状态 k=k+1 (此时k到达了最后状态,k=3,说明在T中找到了P,因此需要一次状态跳转, 当前已经匹配了"abb",找到"abb"的后缀并且是"abb"的前缀,很容易看出不存在这样的后缀,故k跳到0)
因为T[6]!=P[k+1],匹配失败,k还是等于0
因为T[7]=P[k+1],T[7]与P[k+1]匹配成功 k=k+1 (此时k=1)
已经扫描到主串结束,全部匹配结束,在P在T中出现了一次。。。。
以上是有限状态机(也是KMP的基本匹配思想)的匹配过程,而KMP在这个方法的基础上预先分析P从而将跳转位置预先算出来存进了数组中,需要时直接使用,大大加快了匹配速度。。
KMP是对有限状态自动机匹配算法的一种改进,下面来说说KMP对有限状态机匹配的改进。。。
再来个例子:设T[n]="abaabab",P[m]="abab";(便于方便,字符串首地址从1开始)
KMP先是对P的每一个元素构造一个状态跳转位置,过程如下(设数组为next[m])
P[1]="a",真后缀是空串,所以next[1]=0; (注意:最大真后缀是不包括第一个字符的后缀)
P[2]="ab"的真后缀也不是P的前缀,故next[2]=0;
p[3]="aba",真后缀"a"是P的前缀,故next[3]=1;
p[4]="abab"真后缀"ab"是P的前缀,故next[4]=2;
至此状态跳转构造完毕
再来看KMP的匹配过程:
k表示当前匹配状态,起始状态k=0;
P[k+1]=T[1] 进入下一状态 k=k+1; 此时k=1;
P[k+1]=T[2] 进入下一状态 k=k+1; 此时k=2;
P[k+1]=t[3] 进入下一状态 k=k+1; 此时k=3;
P[k+1]!=T[4] 开始状态跳转 因为k此时为3,代表已经匹配了3个,在这三个已经匹配的字符串里找下一个匹配位置,也就是next[k],令k=next[k];(此时k=1) P[k+1]!=T[4] 还不匹配,继续跳转 k=next[k] (此时k=0) P[k+1]=T[4] 匹配成功,进入下一状态 k=k+1 (此时k=1)
P[k+1]=T[5] 进入下一状态 k=k+1 (此时k=2)
P[k+1]=T[7] 进入下一状态 k=k+1 (此时k=3)
P[k+1]=T[8] 进入下一状态 k=k+1 (此时k=4,已全部匹配成功,在T中已经出现了P,所以还需要做一次状态跳转 k=next[k] (此时k=2))
至此KMP匹配算法结束。。。。检测到P在T中出现了一次
下面用代码描述下KMP(下面T和P都是从T[0]和P[0]开始存储的,所以k的起始状态应是-1)