由于传统的字符串匹配效率不高,大概思路:从主串的第pos个字母起和模式的第一个字符比较之,若相等,则继续逐个比较后续字符;否则从主串的下一个字符起再重新和模式的字符比较之,以此类推,直到匹配完主串的每一个子串,如果主串有n个字符,模式串有m(m<n),则在最坏情况下是O(n*m)的复杂度。因此考虑到改进传统的字符串匹配算法。
kmp其实是在有限自动机的基础上改进过来的,因此先考虑有限自动机。它的主要改进点是,当出现主串和模式串某个字符比较时不相等时,不返回到主串上次比较位置的下一个位置,而是通过对模式串进行预处理后,得到一个状态转移表,表中每一个元素是一个状态,根据此表就可以不返回主串的位置,也就是说主串可以一直向后比较,改变标记的只是模式串的位置,也就是下次所在的状态。这样一来在匹配时的时间复杂度就只有n了(主串只扫描了一遍),但是用有限自动机在构建状态转移表时的复杂度是O(m*@),m是他的模式串的长度,也就是所拥有的所有状态数,@就是在这个模式串中出现的字符种类,当建立了这个表,就可以快速的根据当前的字符,和当前的状态找到转移到的状态,伪代码非常的简单:
FINITE_AUTOMATON_MATCHER(T,&,m)
1 n <—length[T]
2 q <—– 0 //q为当前的状态,即模式串中的位置
3 for i <—-1 to n
4 do q <——- &(q,T[i]) //把表中的状态给q
5 if q == m then print ” Pattern occurs with shift” i-m;
这就是对只有一个模式串的有限自动机的方法,下面介绍kmp算法,他的改进地方主要在对模式串的预处理上,对于只有一个模式串的问题,可以仅用一个next数组(含有m个元素)来表示,构建时间为m,这样算法的时间就不受限于不同的模式串了,只要他们的长度一样,运行时间就一样。但是kmp也仅限于处理单模式串的匹配问题,对于 多模式串的匹配,还是要回归到原来的有限自动机的方法,这个在最后介绍。
kmp构造next数组最关键的一个思想就是:对于模式串的第j个字符,next[j]的数值等于在前j个模式串中p1,…,pj,找到一个最长的前缀,这个前缀也是此子模式串的后缀,(很显然,此值必然小于j)这个最长的前缀的字符数就是next[j]的数值。伪代码如下:
COMPUTE_PREFIX_FUNCTION(P)
1 m<— length[p]
2 next[1] <— 0
3 k<— 0
4 for q <– 2 to m
5 do while k>0 and p[K+1] != p[q]
6 do k <— next[k]
7 if p[k+1] = p[q] then k <— k+1
8 next[q] <— k
9 return next
求最大前缀的实现巧妙的运用了最大前缀传递的性质,第6步是关键的语句。复杂度m。
有了这个预处理,在进行匹配时就跟之前的有限自动机一样了。这里省略了伪代码。。。他的复杂度为n
最后说一下对于多模式串的匹配。这个我自己也没有实现过,当时刚好我同学要做这方面的东西,我也顺便了解了一下实现过程。对于多个字符串,只能通过有限自动机创建状态转移,当全部建好后,它就类似于一棵字母树,每一条到叶子的路径代表着一个模式串,然后通过广度优先确定当出现匹配不相等时,转移到的状态。这里如果有很多模式串,而且字符的出现概率,等等都是随机的话,这棵字母树就会非常的复杂,需要一个比较好的结构来存储,平衡二叉树(红黑树)在基于时间空间上的权衡后是一个不错的选择,当然还有其它很多的存储方式。。。。。。本人对于字符串的匹配学习主要是通过MIT的算法导论和同学的几篇论文。