算法导论笔记:32字符串匹配算法

       在编辑文本程序中,经常需要在文本中找到某个模式的所有出现位置。典型的情况是:在一个文本文件中,搜索用户输入的关键字。解决这种问题的算法叫做字符串匹配算法。字符串匹配算法的形式化定义如下:假设文本是长度为n的数组T[1..n],而模式是一个长度为m的数组P[1..m],其中m<=n,P和T中的元素都来自有限字符集。比如∑ = {0,1}或者 ={a,b,…z}。字符数组P和T通常称为字符串。

       如果0<=s<=n-m,并且T[s+1..s+m] = P[1..m],则称模式P在文本T中出现,且偏移为s(意味着模式P在文本T中出现的位置是从s+1开始的)。称s为有效偏移。字符串匹配问题就是找到所有的有效偏移。

       字符串x的长度用|x|表示,两个字符串x和y的连接用xy表示,长度为|x|+|y|。如果x=wy,则称字符串w是字符串x的前缀,记做w《算法导论笔记:32字符串匹配算法》x。类似的,如果x=yw,则称w是x的后缀,记做w《算法导论笔记:32字符串匹配算法》x。例如ab《算法导论笔记:32字符串匹配算法》, cca《算法导论笔记:32字符串匹配算法》abcca。《算法导论笔记:32字符串匹配算法》《算法导论笔记:32字符串匹配算法》的性质如下:

       当且仅当xa《算法导论笔记:32字符串匹配算法》ya时,有x《算法导论笔记:32字符串匹配算法》y;

       如果x《算法导论笔记:32字符串匹配算法》,  y《算法导论笔记:32字符串匹配算法》,如果|x|<=|y|,那么x《算法导论笔记:32字符串匹配算法》y; 如果|x|>=|y|,那么y《算法导论笔记:32字符串匹配算法》x;如果|x| = |y|,那么x=y。

 

一:朴素字符串匹配算法

       朴素字符串匹配算法是通过一个循环找到所有有效偏移,代码如下:

       NAIVE-STRING-MATCHER(T, P)

              n= T.length

              m=P.length

              for s = 0 to n-m

                     if P[1..m] == T[s+1, s+m]

                            print “Patternoccurs with shift” s

       在最坏情况下, 朴素字符串匹配算法运行时间为O((n-m+1)m)。这种朴素算法的效率不高,是因为当其他无效s值存在时,它也只关心一个有效的s值,而完全忽略了检测无效s值时获得的文本信息。而这些信息可能很有用。

 

二:Rabin-Karp算法

       Rabin-Karp算法的预处理时间是O(m),最坏情况下,运行时间为O((n-m+1)m)。但是平均情况下,它的运行时间还是比较好的。

       假设={0,1,2,…9},这样每个字符都是十进制数字。因而可以用长度为k的十进制数表示由k个连续的字符组成的字符串,比如字符串“31415”对应着十进制数31415。

       给定一个模式P=[1..m],假设p表示其相应的十进制数,给定文本T[1..n],假设《算法导论笔记:32字符串匹配算法》表示T[s+1..s+m]所对应的十进制数,其中s=0,1,…,n-m。可以在O(m)时间计算出p《算法导论笔记:32字符串匹配算法》,比如:

《算法导论笔记:32字符串匹配算法》

计算《算法导论笔记:32字符串匹配算法》也类似。

       可以在时间O(n-m)内计算出剩余的《算法导论笔记:32字符串匹配算法》《算法导论笔记:32字符串匹配算法》《算法导论笔记:32字符串匹配算法》可以根据《算法导论笔记:32字符串匹配算法》计算出《算法导论笔记:32字符串匹配算法》

《算法导论笔记:32字符串匹配算法》

       所以,总时间为O(m) + O(n-m) = O(n)的时间。

       以上的结论是针对p《算法导论笔记:32字符串匹配算法》的值不太大的情况,若P包含m个字符,m比较大,则p上的每次算数运算需要“常数”时间这一假设就不合理了。利用素数可以容易的解决该问题:选取一个素数q,计算p和《算法导论笔记:32字符串匹配算法》模q的值,就可以在O(m)时间内计算出模q的p和《算法导论笔记:32字符串匹配算法》的值。这样,上面的式子就变为:

《算法导论笔记:32字符串匹配算法》      

其中h《算法导论笔记:32字符串匹配算法》(mod q)。上面的式子以及下面的程序之所以成立,是基于下面的等式:

       若c=a+b, 则c mod q = (a mod q + b mod q) mod q。

       但是基于模q的结果并不是完全正确的,《算法导论笔记:32字符串匹配算法》(mod q)并不能完全说明p = 《算法导论笔记:32字符串匹配算法》。但是如果p != 《算法导论笔记:32字符串匹配算法》(mod q),则一定可以断定p != 《算法导论笔记:32字符串匹配算法》所以,任何满足《算法导论笔记:32字符串匹配算法》(mod q)的偏移s都需要进一步检测,看s是真的有效偏移,还是一个伪命中点。如果q足够大,则这个伪命中点就能尽量少的出现。代码如下:

       《算法导论笔记:32字符串匹配算法》

       RABIN-KARP算法的预处理时间是O(m),最坏情况下,它的匹配时间是O((n-m+1)m),比如n-m+1个可能的偏移中每一个都是有效的。

       在实际的应用中,因为任意的模q的余数等于p的概率为1/q,所以预计伪命中的次数为O(n/q),每次命中的时间代价是O(m),所以该算法的期望运行时间是:O(n) + O(m(v + n/q)),其中v是有效偏移,如果v = O(1)并且q>=m, 则算法的运行时间为O(n)。

 

三:利用有限自动机进行字符串匹配

       一个有限自动机M是一个五元组(Q,《算法导论笔记:32字符串匹配算法》, A,,δ),其中:

       Q是状态的有限集合;

       《算法导论笔记:32字符串匹配算法》是初始状态;

       A是可以接受状态集合;

       是输入字符集

       δ是状态转换函数,如果有限自动机在状态q读入字符a,则它的状态从q转换为δ(q, a)。即进行了一次状态转移。如果当前状态q属于A时,就说自动机M接受了迄今为止读入的字符串。

       在有限自动机M中,引入终态函数Φ,Φ(w)是在扫描完字符串w后的状态。递归定义函数如下:

《算法导论笔记:32字符串匹配算法》

 

       对于给定的模式P,可以在预处理阶段构造出一个字符串匹配自动机,为了说明与给定模式P[1..m]对应的字符串匹配自动机,首先定义辅助函数σ,称为对应P的后缀函数。满足:σ(x) =max{k:《算法导论笔记:32字符串匹配算法》}也就是P的前缀字符串《算法导论笔记:32字符串匹配算法》同时满足又是x的后缀字符串中,k的最大取值。例如对于模式P=ab, 有σ(ε) = 0,σ(ccaca)=1,σ(ccab)=2。

       对于长度为m的模式Pσ(x)=m,当且仅当《算法导论笔记:32字符串匹配算法》

       如果x《算法导论笔记:32字符串匹配算法》,则 σ(x)<=σ(y).

 

       对于给定模式P[1..m],其相应的有限自动机M定义如下:

       状态集合Q={12.., m}。开始状态《算法导论笔记:32字符串匹配算法》=0,并且只有状态m是唯一被接受的状态。对任意的状态q和字符a,转移函数δ定义如下:δ(q, a) =《算法导论笔记:32字符串匹配算法》

       根据上面的定义,可以得到结论《算法导论笔记:32字符串匹配算法》 =《算法导论笔记:32字符串匹配算法》,下面证明之:

1:对任意字符串x和字符a,σ(xa)<=σ(x)+1

       证明:假设r =σ(xa)。如果r=0,因为σ函数非负,所以成立。假设r>0,如下图:

《算法导论笔记:32字符串匹配算法》

       根据函数的定义有:《算法导论笔记:32字符串匹配算法》a,将a从《算法导论笔记:32字符串匹配算法》的末尾去掉后,得到《算法导论笔记:32字符串匹配算法》,因为如果x《算法导论笔记:32字符串匹配算法》,则σ(x)<=σ(y) ,所以 《算法导论笔记:32字符串匹配算法》<=σ(x),所以r-1 <=σ(x),因而得证。

2:对任意x和字符a,如果σ(x)=q,则σ(xa)=《算法导论笔记:32字符串匹配算法》

       证明:根据函数的定义有《算法导论笔记:32字符串匹配算法》:,如下图所示,有《算法导论笔记:32字符串匹配算法》

《算法导论笔记:32字符串匹配算法》

假设r =σ(xa),则《算法导论笔记:32字符串匹配算法》a,根据上面的证明有σ(xa)<=σ(x)+1,所以r<=q+1,所以|《算法导论笔记:32字符串匹配算法》| <= | 《算法导论笔记:32字符串匹配算法》|,因为如果x《算法导论笔记:32字符串匹配算法》,  y《算法导论笔记:32字符串匹配算法》,如果|x|<=|y|,那么x《算法导论笔记:32字符串匹配算法》y。所以《算法导论笔记:32字符串匹配算法》所以《算法导论笔记:32字符串匹配算法》=《算法导论笔记:32字符串匹配算法》所以r <=《算法导论笔记:32字符串匹配算法》,所以σ(xa) <=《算法导论笔记:32字符串匹配算法》

       又因为《算法导论笔记:32字符串匹配算法》所以σ(xa) >=《算法导论笔记:32字符串匹配算法》,因而得证。

3:如果T[1..n]是自动机的输入文本,则对i=0,1,…n,有 =

       证明:用归纳法证明,当i=0,《算法导论笔记:32字符串匹配算法》《算法导论笔记:32字符串匹配算法》

如果《算法导论笔记:32字符串匹配算法》 =《算法导论笔记:32字符串匹配算法》,现在证明:《算法导论笔记:32字符串匹配算法》 =《算法导论笔记:32字符串匹配算法》.证明过程如下:

      《算法导论笔记:32字符串匹配算法》

       所以: 《算法导论笔记:32字符串匹配算法》=《算法导论笔记:32字符串匹配算法》

算法代码如下:

       FINITE-AUTOMATON-MATCHER(T, )

              n = T.length

              q = 0

              for i = 1 to n

                     q =δ(q, T[i])

                     if q = m

                            print “Pattern occurs with shift” i-m

       《算法导论笔记:32字符串匹配算法》

       COMPUTE-TRANSITION-FUNCTION算法的运行时间为O(《算法导论笔记:32字符串匹配算法》),如果能够利用精心计算出的模式P的有关信息,则根据P计算出δ所需要的时间可以改进为O(m∑)。所以,预处理时间可以达到O(m),匹配时间为O(n)。

 

四:KMP算法

       KMP算法预处理时间为O(m),匹配时间为O(n)。预处理过程主要是根据模式P得到前缀函数的值,具体描述参见《KMP算法》。

 

    原文作者:gqtc
    原文地址: https://www.cnblogs.com/gqtcgq/p/7247218.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞