Test Header HTML Code

基于有限状态自动机分析KMP字符串匹配算法

 

一 定义

1 主串S = s1s2…sn, 即由n个字符组成的字符串

2 模式串T=t1t2…tm,即由m个字符组成的字符串

3 字符串匹配问题定义:给定主串S与模式串T,若S中含有T,返回T第一次出现的位置,否则返回-1.

二 普通做法

for(int i = 0; i < n; i++){

for(int j = 0; j < m && s[i + j]==t[j]; j++);

if(j == m) return i;

}

return -1;

三 有限状态自动机

有限状态自动机可以用5元式定义,即输入字符集,状态集,初始状态集,接受状态集,状态转移函数.

我们常用的正则表达式其实就是有限状态自动机,能够用有限状态自动机表示的语言即为正则语言.

字符串匹配本质也是一个模拟有限状态自动机的过程,即构造一个FA, 该FA只接受所有含有字符串T的字符串.若输入串S被该FA接受,则表明S含有T.

假设T=”ababcab”

其上述普通做法所模拟的FA可以描述如下:

clip_image002

每次内层循环开始,状态为0,若下一个字符串为a,则状态转为1,也即j++,直到j=7,即识别了字符串”ababcab”,到达了接受状态7. 相反,假设在状态2时,下一个输入字符不是’a’,则匹配失败,此时应该重新回归起始状态,在代码中的具体体现就是退出内层循环,i++,再进入内层循环,此时j=0,即重新从状态0开始。

但是上述FA不是通常意义上的FA,其效率非常低,有两个原因:

1) 每次匹配失败,状态重新回归0,即j=0。

2) 每次匹配失败,回退1…j个字符,即i=i+1,本来已经读取到了i+j。

上述两个回退分别体现在内循环与外循环,,比如我们在j=2时发现匹配失败,此时已经读取了i+2个字符,但是退出内层循环,只是i++,再重复读取字符,即从i+1开始重复读取字符,而第i+1,i+2个字符已经被读取了,按道理不应该重复计算。并且状态2应该具有对历史匹配的记忆能力,即状态2表示了0->1->2的过程,表示“ab”已经匹配了。

如何利用这些已知匹配信息来提高计算速度呢?主要从两点入手:

1) 每次匹配失败,状态回归到最近一个等价状态,比如状态3的等价状态应该是1,因为1表示已匹配字符‘a’,而3表示已匹配‘aba’,当然不能说1与3完全等价,只有在接下来的输入不是b时,状态3可看作与1等价因为,此时表示”aba”匹配没有意义,最多只有最后1个“a”还有意义,因为0->1也是表示匹配“a”,所以可以看作3与1等价。

2) 主串不需要回退,即不再需要外层循环。

首先我们可以假设任意一个状态具有两个跳转函数,其中1个若读取匹配的字符,跳转到下一个状态即i++, j++,另外一个即空跳转(读取ε)到初始状态0,当然空跳转只是在不匹配的情况下才使用,即j=0,注意由于是空跳转,即表示不读取任何输入,此时i不变,这也是合理的,因为si!=tj,那么留着si用于接下来的匹配。

根据以上分析,我们可以得出下属FA:

clip_image004

分析:

1) 状态0时,若输入不是a,则0->0;这里不是空跳转,注意若在起始状态如果不断空跳转,那么程序中的表现即i不变,陷入死循环。

2) 对于任意状态k,若不匹配,则需要回退到前面的状态,在前面我们都是让其回归初始状态,为了提高效率,让其回归最近一个“等价”状态,那么如何计算最近1个等价状态呢?

定义“等价”状态函数next[k],设想我们先找k的前状态k-1的等价状态next[k-1],若t[next[k-1]] = t[k-1],即表示next[k-1]->next[k-1]+1与k-1->k的跳转条件相同,那么可定义next[k] = next[k-1]+1,形象来说,即k-1的等价状态next[k-1]表示0,…,next[k-1]-1个字符与k-next[k-1], … ,k-1个字符相同,那么如果第next[k-1] 个字符与k-1个字符相同,相当于0,…,next[k-1] = k-next[k-1],…,k 即表示next[k]=next[k-1]+1,但是这是一个递推的过程,即如果t[next[k-1]] = t[k-1],那么判断是否t[next[next[k-1]]]=t[k-1]

在此可以定义next函数为:

若k=0, 则next[k]=-1/*设其为-1,一方面可以在下述递推中作为终止条件,另外也可以在0状态时,避免空跳转*/

若k>0, 且 t[k-1]=t[next[k-1]], 则next[k]=next[k-1]+1,否则递推判断是否t[k-1]=t[next[next[k-1]]],…;否则next[k]=0

具体程序Java实现如下:

int index(char src[], char pattern[])

{

int[] next = getNext(pattern);

int i = 0, j = 0;

while(i < src.length && j < pattern.length){

if(j == -1){ //起始位置不匹配,继续往前读

i++;

j = 0;

}

else if(pattern[j] == src[i]){ //状态转移

i++;

j++;

}

else j = next[j]; //只改变状态j,i不变,即空跳转

}

if(j == pattern.length) return i - j;

else return -1;

}

int[] getNext(char pattern[])

{

int[] next = new int[pattern.length];

next[0] = -1;

for(int i = 1; i < next.length; i++){

next[i] = 0; //初始化为0

int j = i - 1;

while(next[j] >= 0){ //递推求解next,-1为终止条件,即j=0时

if(pattern[i - 1] == pattern[next[j]]){

next[i] = next[j] + 1;

break;

}

else j = next[j];

}

}

return next;

}

posted on 2010-05-24 08:58  宁静的水泡  阅读(3493)  评论(0编辑  收藏  举报

导航

Test Rooter HTML Code