字符串匹配问题:
假设文本是一个长度为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中出现,且偏移为3;
字符串匹配问题就是找到所有的有效偏移,使得在该有效偏移下,所给的模式 P 出现在给定的文本 T 中。
朴素字符串匹配算法
通过一个循环找到所有有效偏移,该循环对n-m+1个可能的s值进行检测,看是否满足条件T [s+1...s+m]=P [1...m]
T="abaabba"
P="baa"
w=[]
for i in range(len(T)-len(P)+1):
flage=True
for j in range(len(P)):
if P[j]!=T[i+j]:
flage=False
if flage:
w.append(i)
print(w)
Rabin-Karp算法:
步骤:
1.将字符串进行数字编码,
2.查找 T [s+1...s+m]的散列地址=P [1...m]的散列地址 的偏移量s
如果T [s+1...s+m]=P [1...m],则T [s+1...s+m]的散列地址=P [1...m]的散列地址(必然条件,非充分条件,即T [s+1...s+m]的散列地址=P [1...m]的散列地址 ,无法推出T [s+1...s+m]=P [1...m],因为存在散列冲突),故散列地址相同后需要进行字符串对比;
3.为进一步压缩匹配时间,运用霍纳法则,根据Ts计算Ts+1,去掉左侧数字,增加右侧数字
T="abaabba"
P="baa"
t,p='',''
for ts in T:
if ts=='a':
t+='0'
else:
t+='1'
for ps in P:
if ps=='a':
p+='0'
else:
p+='1'
w=[]
for i in range(len(T)-len(P)+1):
if int(t[i:i+len(P)])%13==int(p)%13:
flage = True
for j in range(len(P)):
if P[j] != T[i + j]:
flage = False
if flage:
w.append(i)
print(w)
利用有限自动机进行字符串匹配:
Knuth-Morris-Pratt算法
比较指针的回溯导致暴力匹配效率较低,KMP算法可以做到仅仅移动模式串,比较指针不回溯
前缀:一个字符串从前往后的所有子串(不包含尾部字符)
后缀:一个字符串从后往前的所有子串(不包含首部字符)
最长公共前后缀:前缀和后缀的最长的共有元素的长度
以“ABCDABD”为例,求解每个位置的最长公共前后缀长度:
模式字符串移动位数= 模式字符串与文本字符串以匹配的位数-已匹配字符串(模式字符串的子字符串)的最长公共前后缀
def compute_prefix_function(string):
n=len(string)
next=[0]*n
k=0 #k指向前缀待匹配的位置
for i in range(1,n):
#前一子串有最长公共前后缀,且待匹配位置与当前位置元素不相同,查找再往前的一个最长公共前后缀
while k>0 and string[k]!=string[i]:
k=next[k-1]
if string[k]==string[i]: #待匹配位置与当前位置元素相同
k+=1
next[i]=k
return next
print(compute_prefix_function("ABCDABD"))
方式1,通过计算位置移动匹配
def compute_prefix_function(string):
n=len(string)
next=[0]*n
k=0 #k指向前缀待匹配的位置
for i in range(1,n):
while k>0 and string[k]!=string[i]: #前一子串有最长公共前后缀,且待匹配位置与当前位置元素不相同,查找再往前的一个最长公共前后缀
k=next[k-1]
if string[k]==string[i]: #待匹配位置与当前位置元素相同
k+=1
next[i]=k
return next
def KMP_MATCHER(str_1,str_2):
n=len(str_1)
m=len(str_2)
next=compute_prefix_function(str_2)
k=0
flage=False
while k<n:
if str_1[k]!=str_2[0]:
k+=1
else:
k0=0
for i in range(m):
if k+i<n and str_1[k+i]==str_2[i]:
k0+=1
else:
break
if k0==m:
flage=True
return k
else:
k+=k0-next[k0-1]
if not flage:
return -1
print(KMP_MATCHER("BBC ABCDAB ABCDABCDABDE","ABCDABD"))
print(KMP_MATCHER("BCABCDAB","ABCDABD"))
# print(compute_prefix_function("ABCDABD"))
方式2:
def KMP_MATCHER_1(str_1,str_2):
n = len(str_1)
m = len(str_2)
next = compute_prefix_function(str_2)
k = 0
for i in range(n):
while k > 0 and str_2[k] != str_1[i]: # 前一子串有最长公共前后缀,且待匹配位置与当前位置元素不相同,查找再往前的一个最长公共前后缀
k = next[k-1]
if str_2[k] == str_1[i]: # 待匹配位置与当前位置元素相同
k += 1
print(k)
if k==m:
k=next[k-1]
return i-m+1