document.write('
对于字符串的匹配,最直接的方法就是逐个比较字符串中的字符,这种方法比较容易实现,但是效
率也比较低下。对于这种字符串匹配的问题,除了最常见的直接比较法外,经典的KMP算法也是不
二选择,它能够显著提高运行效率,下面分别介绍这两种方法。
方法一:直接计算法
假定主串S=“S0
S1
S2…Sm”,模式串P=“P0
P1
P2…Pn”。实现方法为:比较从主串S中以Si
(0≤i<m)为首的字符串和模式串P,判断P是否为S的前缀,如果是,那么P在S中第一次出现的位置
则为i,否则接着比较从Si+1开始的子串与模式串P,这种方法的时间复杂度为O(m*n)。此外如果i>
m-n,那么在主串中以Si为首的子串的长度必定小于模式串P的长度,因此,在这种情况下就没有必
要再做比较了。实现代码如下:
"""
方法功能: 判断p是否为s的子串, 如果是, 那么返回p在s中第一次出现的下标, 否则返回-1
输入参数: s和p分别为主串和模式串
"""
def match(s,p):
#检查参数的合理性
if s==None or p==None:
print "参数不合理"
return -1
slen=len(s)
plen=len(p)
#p肯定不是s的子串
if slen<plen:
return -1
i=0
j=0
while i<slen and j<plen:
if list(s)[i]==list(p)[j]:
#如果相同, 那么继续比较后面的字符
i+=1
j+=1
else:
#后退回去重新比较
i=i-j+1
j=0
if(i>slen-plen):
return -1
if j>=plen: #匹配成功
return i-plen
return -1
if__name__=="__main__":
s="xyzabcd"
p="abc"
print match(s,p)
程序的运行结果为:
3
算法性能分析:
这种方法在最差的情况下需要对模式串P遍历m-n次(m,n分别为主串和模式串的长度),因此,
算法的时间复杂度为O(n(m-n))。
方法二:KMP算法
在方法一中,如果“P0
P1
P2…Pj-1”==“Si-j…Si-1”,那么模式串的前j个字符已经和主串中i-j
到i-1的字符进行了比较,此时如果Pj
!=Si,那么模式串需要回退到0,主串需要回退到i-j+1的位置
重新开始下一次比较。而在KMP算法中,如果Pj
!=Si,那么不需要回退,即i保持不动,j也不用清
零,而是向右滑动模式串,用Pk和Si继续匹配。这种方法的核心就是确定k的大小,显然,k的值越
大越好。
如果Pj
!=Si,可以继续用Pk和Si进行比较,那么必须满足:
(1)“P0
P1
P2…Pk-1”==“Si-k…Si-1”
已经匹配的结果应满足下面的关系:
(2)“Pj-k…Pj-1”==“Si-k…Si-1”
由以上这两个公式可以得出如下结论:
“P0
P1
P2…Pk-1”=“Pj-k…Pj-1”
因此,当模式串满足“P0
P1
P2…Pk-1”==“Pj-k…Pj-1”时,如果主串第i个字符与模式串第j个字
符匹配失败,那么只需要接着比较主串第i个字符与模式串第k个字符。为了在任何字符匹配失败的
时候都能找到对应k的值,这里给出next数组的定义,next[i]=m表示的意思为:“P0P1…Pm1”=“Pi-m…Pi-2Pi-1”。计算方法如下:
(1)next[j]=-1 (当j==0时)
(2)next[j]=max (Max{k|1<k<j且“P0…Pk”==“Pj-k-1…Pj-1”)
(3)next[j]=0 (其他情况)
实现代码如下:
"""
方法功能: 求字符串的next数组
输入参数: p为字符串, nexts为p的next数组
"""
def getNext(p,nexts):
i=0
j=-1
nexts[0]=-1
while i<len(p):
if j==-1 or list(p)[i]==list(p)[j]:
i+=1
j+=1
nexts[i]=j
else:
j=nexts[j]
def match(s,p,nexts):
#检查参数的合理性, s的长度一定不会小于p的长度
if s==None or p==None:
print "参数不合理"
return -1
slen=len(s)
plen=len(p)
#p肯定不是s的子串
if slen<plen:
return -1
i=0
j=0
while i<slen and j<plen:
print "i="+str(i)+","+"j="+str(i)
if j==-1 or list(s)[i]==list(p)[j]:
#如果相同, 那么继续比较后面的字符
i+=1
j+=1
else:
#主串i不需要回溯, 从next数组中找出需要比较的模式串的位置j
j=nexts[j]
if j>=plen: #匹配成功
return i-plen
return -1
if __name__=="__main__":
s="abababaabcbab"
p="abaabc"
lens=len(p)
nexts=[0]*(lens+1)
getNext(p,nexts)
print "nexts数组为: "+str(nexts[0]),
i=1
while i<lens-1:
print ","+str(nexts[i]),
i+=1
print \'\\n\'
print "匹配结果为:"+str(match(s,p,nexts))
程序的运行结果为:
next数组为:-1,0,0,1,1
i=0,j=0
i=1,j=1
i=2,j=2
i=3,j=3
i=3,j=1
i=4,j=2
i=5,j=3
i=5,j=1
i=6,j=2
i=7,j=3
i=8,j=4
i=9,j=5
匹配结果为:4
从运行结果可以看出,模式串P="abaabc"的next数组为[-1,0,0,1,1],next[3]=1,说明
P[0]==P[2]。当i=3,j=3的时候S[i]!=P[j],此时主串S不需要回溯,跟模式串位置
j=next[j]=next[3]=1的字符继续进行比较。因为此时S[i-1]一定与P[0]相等,所以,就没有必要再
比较了。
算法性能分析:
这种方法在求next数组的时候循环执行的次数为n(n为模式串的长度),在模式串与主串匹配的过
程中循环执行的次数为m(m为主串的长度)。因此,算法的时间复杂度为O(m+n)。但是由于算法申
请了额外的n个存储空间来存储next数组,因此,算法的空间复杂度为O(n)。
');