您的位置:首页 > 其它

KMP算法 字符串模式匹配

2017-03-18 15:03 316 查看
    问题:在字符串S = "ABCABCABD",T = "ABCABD",在字符串S中中查询子串T的位置。

    原始的算法大家都可以想到,设一个指针i一个指针j,指针i指向S,j初始为0指向子串T。当S[i] == T[0]时,比较S[i+j]是否都等于T[j]。如果全都匹配的话,输出i就是位置。否则的话,就要从S[i+1]的位置再开始这个过程。

过程如下:



    根据图中的过程,我们可以知道朴素算法查找这种情况i要移动4步,过程中经历多次判别失败才能找到答案。那么用KMP算法能有多快呢?不多废话,直接上图体验



    看图上就只有两步过程!!怎么做到的!耐心看我解释:

初始i=0,j=0,字符串S和待查询的子串T中S[i] == T[j] == 'A',i++,j++
重复过程1,S[i] == T[j] ,i++,j++。
直到S[5]=='C',T[5]=='D'时,两个不等,但是经过预先处理(后面解释),我们知道了T[0]T[1]和T[3]T[4]的字符串相同都是"AB",而且T[2] != T[5],那么T[0]T[1]一定匹配S[3]S[4],T[2]有可能匹配S[5]。
这事i不变还是等于5,j=2。S[i]==T[j]=='C'。匹配成功。i++,j++,继续过程1。
继续判断下去,i==8,j==5时,匹配完成查询成功。

    KMP算法就是这么高效时间复杂度O(n+m),也不算复杂,核心思想是对待匹配的子串L预先进行处理,预先知道其中有哪些部分是相同的相互匹配的。当在S中查询,发现某个字符不匹配时,L字符串不需要回溯到第一个字符从头开始再找一边,而是由于预先知道有部分是相互匹配的,跳过匹配的部分再开始查找。这里对于指向S的指针i是随着j不断增加的,而不是像朴素的算法中那样,指向S的指针是由i+j两部分组成。

    问题的关键是如何预先知道子串L中,每个字符一旦匹配失败时,下一步j应该等于几才能继续比较S[I]和T[j]。“预知”的过程是先建立一个next[j]数组,每当某个字符L[j]匹配失败时,有j = next[j],再对S[i]和T[j]进行比较。注意,存在有next[j]==-1,当j = next[j]=-1时,表示子串中已经没有可以和S[i]匹配的字符,对i++,j=0,进行下一次匹配。

生成next[j]的函数有多种方式,我理解的一种代码如下:

void Next(char * L , int * next) {
int j ;
int k ;
next[0] = -1 ;
j = 0 ;
k = -1 ;
while(j < strlen(L) - 1)
{
if( k==-1 || L[k]==L[j])
{
j ++ ;
k ++ ;
next[j] = k ;
}
else
{
k =next[k] ;
}
}
}

有了next函数后,我们可以很方便的使用它实现KMP算法:

while(S[i] != '\0' && L[j] != '\0')
{
if(S[i] == L[j])
{
i++ ;
j++ ;
}
else
{
if(next[j] == -1)//表示L中已经没有可以和S匹配的子串了。比如L[0]也不能匹配S[i]
{
i ++ ;
j = 0 ;
}
else
{
j = next[j] ;
}
}
}


    最后:本人也是水平有限,不断在自学,写博客的目的一来是加强自己,二来想用比较通俗的方式说明一个问题的基础,用新人的方式像其他新人分享,我想这样应该更有助于新人快速入门。当然了,在了解了这些基础后,还是可以去看看大牛们写的,我写的毕竟还是片面。祝大家学业有成!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: