Mastermate官网 香港|英国|新加坡|澳大利亚|澳门|深圳硕士研究生申请平台

kmp算法介绍 2012 -4-8

作为一个从事acm一年的菜鸟,写一写自己的心得:

这是从网上档的kmp算法的介绍,很有启发性。

KMP算法,每一个初学者都曾被它搞迷糊,当年我就是强行记住的,但是记得越快,忘得越快。学完数据结构两年后,重新回头看这个当年束之高阁的算法,才有了更深的体会。

 

我们假设一个场景,你手上拿着一串红蓝两种颜色的珠子,墙上挂着一串更长的珠子,同样是红蓝两色的,你的任务就是找出和你手中珠子排列顺序相同的一段。

 

最简单也最容易想到的方法,就是从墙上第一颗开始,拿手中的珠子挨个去和墙上珠子去比,都相同那就OK了,不相同再从墙上第二颗开始,以此类推。

 

有人会想:如果我手里的珠子都是红色的,我还用这种方法,我傻呀?对,Knuth当年也是这样想的。

你看,假设你手里是连续的100颗红色珠子,墙上从第一颗开始是99颗红色珠子,那么当你比到第100颗你才发现少了一颗,你要从第二颗开始再去数一遍吗?没人会这么做,除了程序员。

 

当然,正常人都要从第100颗之后再去找连续的红色珠子。有了这样的觉悟,我们可以开始进入KMP算法了。

 

现在我们开始使用术语“串”。手上的珠子称为模式串,墙上的珠子称为目标串。抛开珠子的比喻,我们的任务就是从目标串当中寻找模式串。

 

原始模式匹配算法最浪费时间的操作在于回溯,每当不匹配,就要回头再做比较。KMP算法用一种巧妙的方法避免了目标串的回溯。也就是说,目标串从头到尾只需要扫描一遍。

 

到这里,产生了两个疑问:不回溯目标串是可能的吗?任何情况下都可以不回溯吗?

第一个问题,我们前面已经肯定了,例如模式串是连续100个a时,我们不需要回溯。

第二个问题,我们的担心是,例如前5个相同,第6个不同,难道你能直接从目标串第7个开始和模式串比较?万一从第2个开始恰好和模式串匹配,你就漏掉了。

 

而KMP的神奇就在于,如果第6个不同,那么接下来我会拿模式串的第1个至第5个之间的一个来和目标串第6个比较,至于具体是哪个,由定位函数决定,这个后面再说。

这个方法保险吗?何止保险,万无一失!这就证明给你看:

当模式串第6个字符与目标串第6个字符失配时,将模式串从第1个字符开始的子串与目标串于第5个字符结束的子串比较,有五种情况:

1 模式串第1-4个字符与目标串第2-5个字符相同

2 模式串第1-3个字符与目标串第3-5个字符相同

3 模式串第1-2个字符与目标串第4-5个字符相同

4 模式串第1个字符与目标串第5个字符相同

5 不存在模式串从1个字符开始的某个子串与目标串于第5个字符结束的某个子串相同

无论目标串与模式串如何变化,总也逃不出这五种情况,而这五种情况的后续比较方法正好就是拿模式串1至5中间的的一个来继续比较。例如第一种情况,前4个相同,那当然从模式串第5个字符开始与目标串第6个字符比较;而像第五种情况,前面没有相同的,那自然从模式串第1个字符开始。

 

这就说明了,任何情况下,只要我们在失配时能计算出下一次应该从模式串第几个字符开始与目标串失配处的字符继续比较,那么目标串就不需要回溯。这个计算过程就是在定位函数中进行的。

 

理论分析到这里,下面是KMP算法流程:

1 比较目标串与模式串的第1个字符,相等则继续比较第2个,直到失配。

2 失配时,假设这时是主串的第i个,模式串的第j个,拿模式串的f(j)个继续和主串第i个进行比较。

2.1 如果相同,那么再挨个比下去

2.2 如果不同,那么重复步骤2

3 比到模式串的最后一位仍然相同,则完成任务

4 比到主串的最后一位仍然未完成任务,则放弃

 

第2步的f(j)是定位函数,先不要管它为什么这样神奇,总之它能告诉你究竟该比较哪一个。看起来仿佛是神的指引呀:你只管比,出错了让神来告诉你由哪一个接着比。

 

KMP的流程就这么简单。那么,剩下的任务就是剖析一下定位函数f了,这才是算法最关键的地方。

假如说,函数f与目标串有关系,那么在f运行过程中必然会造成目标串的回溯,KMP算法就失去意义了。所以说,f与目标串一点关系也没有,只需要模式串就能计算,参数是失配位置j。

假设某一个模式串里,f(6) = 3的话,这意味着什么呢?

就是说如果第6个位置失配了,那么下一步我直接拿模式串第3个来和目标串串第6个比较,之所以能这么比,只能是因为模式串的第1、2个和目标串的第4、5个是匹配的,但是目标串的4、5个和模式串的4、5个也是匹配的,由相等关系的传递性,我们得知模式串的1、2个和4、5个是相等的。

 

这给了我们提示,如果模式串内部存在一对相同的片段,且其中一个片段要从模式串的第一个字符开始,例如123和345相同,或者12和56相同这样的情况,那么当失配恰好发生在后一个相同片段的结尾之后时,我们就可以从前一个相同片段的结尾后继续比较。举个例子:

    1 2 3 4 5 6 7 8 9 …

A = a b a b a b a c b a b …

B = a b a b a c b

B中123与345是相同的片段。第一次失配时,i=j=6,恰好发生在B的345片段结尾后,所以下一次比较时就用123片段结尾后,即B的第4个字符与A的第6个字符进行比较,即f(6)=4。

 

而如果模式串不存在上面所描述的片段,那么就说明目标串失配位置之前的部分不会再有匹配了(否则矛盾了),我们可以从目标串失配位置的后一个位置开始比较了。

 

因此,要计算定位函数f(j),就要先寻找与模式串中终止于j-1位置的某个片段相同的片段,而且与之相同的那个片段恰好要从模式串的第一个字符开始,如果找到了,假设该片段是第1个字符到第n个字符,即1…n与j-n…j-1相同,则f(j)的值为n+1。如果找不到这种片段,则f(j)=1。特别的,如果模式串的第一个字符就不匹配,规定f(1)=0,表示失配位置前不可能有匹配了,这时将目标串的指针递增,从失配位置的后一个继续比,当然本文中所有的序号都是从1而不是0开始的。

所以对于上面的串B,每个位置上f的值分别是:0 1 1 2 3 4 1 1

 

再来个例子吧:P= abaabcac,P中每个位置上f的值

j    1 2 3 4 5 6 7 8

P    a b a a b c a c

f(j) 0 1 1 2 2 3 1 2

 

至此KMP算法的精髓应该介绍完了吧,还剩下最后一点,就是所谓的f修正值。这个是在求f(j)的过程中修正的,不修正也不影响匹配结果,修正算是一种优化吧。

 

如果在j失配时,不是要跳到f(j)吗?而如果又失配了,不是要跳到f(f(j))吗,如果又失配,就跳到f(f(f(j)))……

为了省略不停的跳的过程,我们注意到,如果第j个和第f(j)个是相同的,那么j失配了,f(j)一定失配。既然这样,那又何必再比呢,所以计算f值的时候,就判断一下字符是否相同,如果相同,就直接使用后一个f值。例如,P中f(3)=1,而1和3位置上的字符是一样的,如果3不匹配,f(3)肯定也不匹配,所以可以直接优化f(3)=f(1)=0。

posted @ 2012-04-08 22:26  大嘴鸟  阅读(157)  评论(0编辑  收藏  举报
Mastermate官网 香港|英国|新加坡|澳大利亚|澳门|深圳硕士研究生申请平台