南师大算法笔记-字符串匹配 Rabin karp 思路以及代码实现

基本问题:给定一个特殊的模式(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[m1]+d(P[m2]+d(P[m3+......+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[m1]+d(T[m2]+d(T[m3+......+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]dm1(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(tsdm1T[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[m1]+d(P[m2]+......+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(tsdm1T[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散列这样就不限制输入字符的类型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值