想想模式匹配那种暴力的逐一比较的普通算法,确实冥冥中能感觉到在主串中不断回溯,确实是一件效率低下的事情,但是就是不知道如何改进。每当此时便不由得为发明KMP算法的三位科学家致敬,他们真是计算机界的大牛,值得我们每一个人学习,我查了下唐纳德·克努特,果然是斯坦福的大牛。。。。。。 他们才是真正的为计算机科学做出贡献的人!学习KMP,不是为了记忆KMP,要理解KMP的思想是什么? 他们是怎么构思出KMP?
Donald Knuth
首先看下普通模式比较算法,已知主串s和模式串p,如下所示,
0 1 2 3 4 5 6 7 8 9 10 11 12
s(主串): a b a b c a b c a c b a b
p(模式): a b c a c
我们对如上模式串p,依次比较,
s0=p0;
s1=p1;
s2!=p2;
s串回溯到s1,使其对准p0开始比较,第二步,
s(主串): a b a b c a b c a c b a b
p(模式): a b c a c
第三步,
s(主串): a b a b c a b c a c b a b
p(模式): a b c a c
第四步,
s(主串): a b a b c a b c a c b a b
p(模式): a b c a c
第五步,
s(主串): a b a b c a b c a c b a b
p(模式): a b c a c
第六步,
s(主串): a b a b c a b c a c b a b
p(模式): a b c a c
在以上比较的过程中,我们发现s串不断的回溯,每次只能步进1,这样缓慢的运行,完全忽略了已比较的信息。我们仔细回味一下第三步的比较:
s(主串): a b a b c a (b) c a c b a b
p(模式): a b c a (c)
等进行到s6时,我们发现,
s6!=p4
通过这个我们得出什么,是的,p串的前4个字符与p2~p5的字符都相等。是的,我们推算出了p2~p5也是 a b c a 这四个字符,如果你坚持用普通模式匹配的话,下一次你的确要拿s3(a b c a)与p0(a)比较,再下一步要拿s4(a b c a)与p0(a)比较,再下一步拿s5(a b c a)与p0作比较。(这的确是第四、五
六步啊)。
很明显,在上面的分析中,我们完全不用考虑s串,只需仔细考察p串即可!如果模式串s的第j个字符发生了与主串的不匹配情况,然后下一步应该与模式串的第k个字符进行比较,那么,可以得出一个等式,
P0 P1... Pk-1 = Pj-k Pj-k+1...Pj-1
这个式子的意思是什么呢?请看下面例子,
0 1 2 3 4
a b c a c
如果在索引位置4处出现了模式串与主串的字符不相等,那么我们应该从模式串的1处开始比较,也就是此时k=1,即
P1-1 = P4-1; 实际为p0=p3;
也就是不相等处的前几个字符等于模式串的从0开始的几个字符!
那么,根据这个算法,我们该如何顺序匹配呢?这是我们的任务,第一步还是和原先一样,等比较到2时,发现字符不一致,
s(主串): a b a b c a b c a c b a b
p(模式): a b c a c
并且,c的前一个字符b在前面没有,所以,还是从模式串的第0号开始,即a,
第二步,最关键的是这种算法主串的位置不回溯到1,还是在2的位置,等到比较到6时发现字符不一致,
s(主串): a b a b c a b c a c b a b
p(模式): a b c a c
第三步,如果我们只是暴力的将模式串的开始放到6号位置时,发现我们卡错地方了!
s(主串): a b a b c a b c a c b a b
p(模式): a b c a c
应该将模式串的开始字符a卡在主串的5处,那么怎么要卡在5处呢?这就是主串不用回溯后,模式串需要考虑的一个问题,要保证卡放的位置正确,这也是为什么我们需要next[j]的原因所在!
请看,现在在a b c a c,这个c处不对应了,那么它的前一个字符a正是开始字符a。
s(主串): a b a b c a b c a c b a b
p(模式): (a) b c a c
匹配完成。现在如何求得这个教科书中的next(j)=k呢? 其中j表示在模式串的第j个字符与主串的不匹配了,k表示应该从模式串的k处开始与主串匹配!
下面给出一个求解next的演示例子,假如有这么一个模式串:
j 1 2 3 4 5 6 7 8
模式串 a b a a b c a c
k=next[j] 1 1 1 2 2 3 1 2
要求解每个字符的失配值,可这样一个一个的来,假定next[0]=0,next[1]=1,
j 1 2
模式串 a b
k=next[j] 1 1
ab字符串的子缀a,a的最大前后缀都为空,
j 1 2 3
模式串 a b a
k=next[j] 1 1 1
aba字符串的子缀ab,ab的相同最大前后缀也未空,
j 1 2 3 4
模式串 a b a a
k=next[j] 1 1 1 2
abaa字符串的子缀aba,aba的相同最大前后缀为a,所以next[4]=2,
j 1 2 3 4 5
模式串 a b a a b
k=next[j] 1 1 1 2 2
abaab字符串的子缀abaa,abaa的相同最大前后缀为a,所以next[5]=2,
j 1 2 3 4 5 6
模式串 a b a a b c
k=next[j] 1 1 1 2 2 3
abaabc字符串的子缀abaab,abaab的相同最大前后缀为ab,所以next[6]=3,
j 1 2 3 4 5 6 7
模式串 a b a a b c a
k=next[j] 1 1 1 2 2 3 1
abaabca字符串的子缀abaabc的相同最大前后缀为空,所以next[7]=1,
j 1 2 3 4 5 6 7 8
模式串 a b a a b c a c
k=next[j] 1 1 1 2 2 3 1 2
abaabcac字符串的子缀abaabca的相同最大前后缀为a,所以next[8]=2,
我们谈论过,已知模式串的话,我们立即就可以进行这个预处理计算了,根据不用理睬主串长什么样子,因为它们是独立的。现在用数学归纳法来详细求解next函数。如果第j+1位失配时,我们应该填写next[j+1]为多少?实际上就是求解next[j+1]即是找从0到j这个子串的最大前后缀
关于求解next[j]=?的问题,有很多这方面的博客,理论方面的推导可以参考严蔚敏老师的。我仔细想了很长时间,还是不能完全想清楚严蔚敏老师的数学辩证法的推导。接下来慢慢琢磨吧,可能需要一些灵感,然后再总结到这里。