Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

算法-发明KMP算法的唐纳德·克努特是怎么想到失配函数next[j]的?

$
0
0

想想模式匹配那种暴力的逐一比较的普通算法,确实冥冥中能感觉到在主串中不断回溯,确实是一件效率低下的事情,但是就是不知道如何改进。每当此时便不由得为发明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]=?的问题,有很多这方面的博客,理论方面的推导可以参考严蔚敏老师的。我仔细想了很长时间,还是不能完全想清楚严蔚敏老师的数学辩证法的推导。接下来慢慢琢磨吧,可能需要一些灵感,然后再总结到这里。

作者:daigualu 发表于2017/3/20 13:05:24 原文链接
阅读:81 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>