基本问题:给定一个特殊的模式(pattern)是否在一个文本(Text)中出现,并且找出模式在文本中所有出现的位置。
下面简单描述下几个定义:
定义1 一个文本(text)是由某个字符集
∑
\sum
∑ 中符号组成的字符序列。
定义2数字T[1…n]表示一个有n个字符的字符序列或者称之为字符串。
定义3 一个模式pattern 是另一个有相同符号集的字符串,采用P[1…m] 表示有m个字符的模式,其中m小于n。
定义4 给定文本T[1…n]和模式P[1…n], 文本的子串T[s+1,s+m],成为有移位的s的字符串,并记为Ts=T[s+1…s+m]。
定义5 如果Ts=P[1…m] 那么称模式P与文本T从s+1 处 开始匹配,并称s是一个合法的移位。
问题的定义:字符串匹配问题是为一个模式p找出文本T的所有合法移位
Naive method 朴素方案
for s<-0 to n-m
if P[1....m]=T[s+1...s+m]
then print " find vaild shift"
end if
endfor
END
Rabin karp 算法
例如
∑
=
{
0
,
1
,
.
.
.
.
9
}
\sum=\{0,1,....9\}
∑={0,1,....9},把P[1…m]看成一个m 位十进制数字,并假设他们的值是p。给定文本T[1…n]有移位s的字符段Ts=T[s+1,s+m], 有m位的十进制数字,并假设它的数值是ts 如果移位s是 一个合法移位,则必有ts=p。
【题外话】这个算法将字符抽象成数字,然后通过比较数字的值来判断是否是一个合法的移位,虽然想法不错,但并没有感到好的优势,也许下面会体会到。
Horner法计算p
p=P[1…m]=P[1]*10{m-1}+P[2]*10{m-2}+…+P[m]
当
∑
\sum
∑是任意集合时,
∣
∑
∣
|\sum|
∣∑∣=d ,那么P[1…m]则对应一个有m位的d进制数p,则Horner 公式 ,可泛化为:
p
=
P
[
m
]
+
d
(
P
[
m
−
1
]
+
d
(
P
[
m
−
2
]
+
d
(
P
[
m
−
3
+
.
.
.
.
.
.
+
d
(
P
[
2
]
+
d
(
P
[
1
]
)
)
]
)
)
)
p=P[m]+d(P[m-1]+d(P[m-2]+d(P[m-3+......+d(P[2]+d(P[1]))])))
p=P[m]+d(P[m−1]+d(P[m−2]+d(P[m−3+......+d(P[2]+d(P[1]))])))
如何计算t_0
t_0的计算方法和p的计算方法相似,只是使用文本串T而不是模式串P,即
t
0
=
T
[
m
]
+
d
(
T
[
m
−
1
]
+
d
(
T
[
m
−
2
]
+
d
(
T
[
m
−
3
+
.
.
.
.
.
.
+
d
(
T
[
2
]
+
d
(
T
[
1
]
)
)
]
)
)
)
t_0=T[m]+d(T[m-1]+d(T[m-2]+d(T[m-3+......+d(T[2]+d(T[1]))])))
t0=T[m]+d(T[m−1]+d(T[m−2]+d(T[m−3+......+d(T[2]+d(T[1]))])))
如何计算ts+1的值
也就是如何计算进行s移位后的t值,我们先推到下从t_0到t_1的过程,已知
t
0
=
T
[
1...
m
]
t_0=T[1...m]
t0=T[1...m]
t
1
=
T
[
2
,
m
+
1
]
=
T
[
m
+
1
]
+
d
(
T
[
2....
m
]
)
=
T
[
m
+
1
]
+
d
(
T
[
1....
m
]
−
d
m
−
1
(
T
[
1
]
)
)
\begin{aligned} t_1&= T[2,m+1] \\ & = T[m+1]+d(T[2....m]) \\ & =T[m+1]+d(T[1....m]-d^{m-1}(T[1])) \\ \end{aligned}
t1=T[2,m+1]=T[m+1]+d(T[2....m])=T[m+1]+d(T[1....m]−dm−1(T[1]))
下面拓展到ts+1
t
s
+
1
=
T
[
m
+
s
+
1
]
+
d
∗
(
t
s
−
d
m
−
1
∗
T
[
s
+
1
]
)
\begin{aligned} t_{s+1}&=T[m+s+1]+d*(t_s-d^{m-1}*T[s+1]) \end{aligned}
ts+1=T[m+s+1]+d∗(ts−dm−1∗T[s+1])
遇到难题
挑战 用O(1)时间从t_s计算t_s+1,每一步的算术操作都必须在常数时间内完成,此要求在m和n很大是无法完成的。
【题外话-问题】为啥无法在常数时间内完成?或许是d的m次方计算时,会需要很大的计算量。能不能把这个系数保存下来呢?
【题外话-回答】回答上面的问题,通常我们会将字符串进行取hash 这样生成的数字肯定不是10进制,有一点大 比如1234444,同是如果m是一个很大的数字,那么经过m次迭代后,p的值也会很大,所以使用取模操作可以控制数值的维度。
【题外话-问题】为什么模质数?取模后会不会因为重复,而造成误判(碰撞)?
【题外话-答案】模质数可以有效的减少碰撞,且去一个较大的质数,可以将碰撞降到足够小如下图所示。
改进算法
选择一个质数q作为模数,使得所有的运算都会在模q下进行,这样所有的运算结果都不会超过g,做法如下:
假设
∑
\sum
∑={0,1…d+1} ,选质数q,使得乘积dq可以存放在计算机一个单元内,迭代公式修改为:
p
=
(
P
[
m
]
+
d
(
P
[
m
−
1
]
+
d
(
P
[
m
−
2
]
+
.
.
.
.
.
.
+
d
(
P
[
2
]
+
d
(
P
[
1
]
)
)
]
)
)
)
m
o
d
(
q
)
)
m
o
d
(
q
)
\begin{aligned} p=(P[m]+d(P[m-1]+d(P[m-2]+......+d(P[2]+d(P[1]))])))mod(q))mod(q) \end{aligned}
p=(P[m]+d(P[m−1]+d(P[m−2]+......+d(P[2]+d(P[1]))])))mod(q))mod(q)
t
s
+
1
=
(
T
[
m
+
s
+
1
]
+
d
∗
(
t
s
−
d
m
−
1
∗
T
[
s
+
1
]
)
)
m
o
d
(
q
)
\begin{aligned} t_{s+1}&=(T[m+s+1]+d*(t_s-d^{m-1}*T[s+1]))mod(q) \end{aligned}
ts+1=(T[m+s+1]+d∗(ts−dm−1∗T[s+1]))mod(q)
这样可以保证数值保持在一个合法的范围内,但是是否可以保证他不会重复呢?【我觉得 可以选择一个比较大的质数,这样应该可以减少重复,但是没有严格的证明】
Rabin karp代码实现:
# 2020/11/20
def Rabin_karp(T,P,d,q):
h=pow(d,len(P)-1)%q
n,m=len(T),len(P)
if m>n:
print("erro ")
return
p,t_0=0,0
flag=0
for i in range(0,m):
p=(d*p+int(P[i]))%q
t_0=(d*t_0+int(T[i]))%q
#以上函数用于初始化
t_s=t_0
for s in range(0,n-m+1):
if p==t_s:
if P[:m]==T[s:s+m]:
print("find vaild shift ,in position {}".format(s))
flag=1
else:# for 循环中已经设置过限制了
t_s=(int(T[s+m+1])+d*(t_s-h*int(T[s+1])))%q
if not flag:
print("unfind the valid shifts")
T="123"
P="1"
Rabin_karp(T,P,2,1610612741)
其实可以对字符串中的每个字符进行hash散列这样就不限制输入字符的类型。