问题
在文本text中查找模式pattern出现的所有位置(text长度为n,pattern长度为m,n,m都是正整数且n>m)。
解法
没有学习AC自动机前想要理解KMP算法非常困难,KMP算法可以看作只有一个模式的AC自动机的简化版。所以将KnuthMorrisPratt放在AhoCorasickAutomata之后,请读者在学习KMP算法之前先阅读AhoCorasickAutomata。
将AC自动机应用在只有一个模式的匹配时,我们会发现这样的AC自动机中没有输出指针,只有失败指针。为了简化我们不再使用树形结构体,而用数组下标来表示失败指针:
得到模式pattern的每个节点跳转的下标,在KMP算法中,这个跳转的下标数组称为失败函数(Failure Function),或部分匹配表(Partial Match Table)。部分匹配表的实质也是最长后缀字符串。
当匹配到text[0…3]=pattern[0…3]但text[4]=pattern[4]时,已知pattern[0…3]的最长后缀字符串为pt[0…1],按照AC自动机的算法,当前的匹配位置是pattern[3],沿着失败指针pattern[3]→pattern[1]跳转,然后继续尝试匹配pattern[2]和text[4]。指向前缀树根节点的下标都设为−1。
由此可得,对于text[i]=pattern[j],若j=0则文本上的位置右移一位i=i+1,匹配上的位置不动;若j>0则模式上的匹配位置跳转到j−1=pmt[j−1]即j=pmt[j−1]+1,文本上的位置不动。然后继续尝试匹配text[i]和pattern[j]。对于text[i]=pattern[j],则文本和模式上的位置都右移一位i=i+1,j=j+1。当j为模式pattern的末尾字符,并且text[i]=pattern[j]匹配成功,这时我们仍然将两个位置右移一位i=i+1,j=j+1继续匹配,那么显然有text[i]=pattern[j](因为模式在这个位置已经没有字符了),这时j的跳转位置为j=pmt[j−1]+1,然后就可以正常匹配了。
根据AC自动机中构造前缀树及失败指针的算法可知:
(1) 对于模式上的位置j=0(前缀树根节点的第一层孩子节点),其失败指针为pmt[j]=−1;
(2) 对于模式上的位置j>0,其父节点位置为j−1,父节点的失败指针位置为pmt[j−1],而失败指针的孩子节点的位置必然是pmt[j−1]+1。若pattern[j]=pattern[pmt[j−1]+1],则可知失败指针为pmt[j]=pmt[j−1]+1;否则失败指针为pmt[j]=−1:
即公式:
pmt[j]=⎩⎨⎧−1−1pmt[j−1]+1j=00<j<m,pattern[pmt[j−1]+1]=pattern[j]0<i<m,pattern[pmt[j−1]+1]=pattern[j] 实际编程中为了方便操作数组下标,通常会定义数组next,令next[i]=pmt[i−1]。
KMP算法的时间复杂度为O(n+m)。
源码
KnuthMorrisPratt.h
KnuthMorrisPratt.cpp
测试
KnuthMorrisPrattTest.cpp