KMP算法解析

KMP算法

时间复杂度O(n+m)实现两个字符串的匹配

所谓字符串匹配,是指“字符串P是否为字符串S的子串,如果是,它出现在S的哪些位置?”其中S称为主串;P称为模式串。

按惯例,主串和模式串都从0开始编号。

字符串匹配时一个非常频繁的任务。

Brute-Force

首先,我们应该如何实现两个字符串A,B的比较?所谓字符串比较

枚举 i=0,1,2.。。len(S)-len(P)

将S[i:i+len(P)]与P作比较。如果一致,则找到了一个匹配。

现在我们来模拟Brute-Force算法,对主串“AAAAAABC”和模式串“AAAB”做匹配:

讨论它的时间复杂度。记n=|将S|为串S的长度,m=|P|为串P的长度。

考虑字符串比较这个小任务的复杂度。最坏情况发生在:两个字符串唯一的差别在最后一个字符。这种情况下,字符串比较必须走完整个字符串,才能给出结果,因此复杂度时O(len(P))的。

由此,不难想到最坏情况下每次都需要比较|P|次,总共需要比较|S|-|P|+1,因此中时间复杂度时O(|P|*(|S|-|P|+1))。考虑

到主串一般比模式串长很多,故Brute-Force的时间复杂度是O(|P|*|S|),也就是O(nm)的。

改进思路

我们很难改进字符串比较的复杂度(因为比较两个字符串,真的之恶能诸葛比较字符)。因此,我们考虑降低比较的趟数。

要优化一个算法,首先要回答的问题是“我手上有什么信息?”我们受伤的信息是否足够,是否有效,决定了我们能把算法

优化到何种程度。请记住:尽可能利用残余的信息,是KMP算法的思想所在。

在Brute-Force中,如果从S[i]开始的那一趟比较失败了,算法会直接开始尝试从S[i+1]开始比较。

这种欣慰,属于典型的“没有从之前的错误中学到东西”。我们应当注意到,一次失败的匹配,会给我们提供 宝贵的信息

如果S[i:i+len(P)]与P的匹配是在第r个位置失败的,那么从S[i]开始的(r-1)个连续字符,一定与P的前(r-1)个字符一模一样

需要实现的任务是“字符串匹配”,而每一次失败都会给我们换来一些信息,能告诉我们,主串的某一个子串等于模式串的摸一个前缀。

跳过不可能成功的字符串比较

next数组

next数组是对于模式串而言的。P的next数组定义为:next[i]表示P[0]~P[i]这一个子串,使得前k个字符恰等于后k个字符的最大的k。

如果把模式串视为一把标尺,在主串上移动,那么Brute-Force就是每次适配之后只右移一位;改进算法则是每次失配之后,移很多位,跳过哪些不可能匹配成功的位置。

那么该如何确定要以多少位呢?

回忆next数组的性质:P[0]到P[i]这一段字串中,前next[i]个字符与后next[i]个字符一模一样。既然如此,如果失配

在P[r],那么P[0]~P[r-1]这一段里面,前next[r-1]个字符恰好和后next[r-1]个字符相等--也就是说,我们可以拿长度为next[r-1]

的那一段前缀,来顶替当前后缀的位置,让陪陪继续下去

P[i]失配后,把P[next[i-1]]对准刚刚失配的那一位。

next数组为我们如何移动标尺提供了依据。

def getNxt(x):
    for i in range(x,0,-1): #从x到1枚举
        if p[0:i] == p[x-i+1:x+1]:
            return i #如果前缀等于后缀,则返回
    return 0
nxt = [getNxt(x) for x in range(len(p))]

接下来,实现利用next数组

def search():
    tar = 0 # tar: 主串将要匹配的位置
    pos = 0 # pos: 模式串中将要匹配的位置
    while tar < len(s):
        if s[tar] == p[pos]: #若两个字符相等,则tar,pos各进一步
            tar+=1
            pos+=1
        elif pos:   #失配了,若pos!=0,则依据next数组移动标尺
            pos = nxt[pos-1] 
        else:       #pos[0]失配了。直接把标尺右移一位。
            tar+=1
        if pos == len(p):   #pos走到了len(P),匹配成功
            print(tar-pos)  #输出主串上的匹配七点,也就是tar-pos
            pos = nxt[pos-1]#移动标尺,继续下一个位置的匹配

#复杂度分析 乍一看,pos值可能不停的变成next[pos-1],代价会比较高;但我们使用摊还分析,显然pos值一共顶多

#自增len(S)次

#快速求next数组

#P自己与自己做匹配

#定义k-前缀为一个字符串的前k个字符

#next[x]定义为:P[0]~P[X]这一段字符串,使得k前缀等于k后缀的最大的k

# 如果一直next[0],next[1],..next[x-1]。如何求出next[x]

# 分情况讨论。已经知道了next[x-1]记为now,如果P[x]与P[now]一样,那最长相等前后缀的长度就可以扩展一位,很明显next[x]=now+1

# 如果不一样,那应该缩小这个now,再来试试P[x]是否等于P[now].

# now应该缩小到多少呢?

# 使得A的k-前缀等于B的k-后缀的最大的k

# 串A和串B是相同的。B的后缀等于A的后缀,因此,使得A的k-前缀等于B的k-后缀的最大的k,其实就是串A的最长公共前后缀的长度next[now-1]

# 当P[now]与P[x]不相等时,我们需要缩小now--把now变成next[now-1],直到P[now]=P[x]为止,就可以直接向右扩展了。

nxt = []
def buildNxt():
    nxt.append(0)#next[0]必然是0
    x = 1 #因此从next[1]开始求
    now = 0
    while x<len(p):
        if p[now] = p[x]:#如果p[now] == p[x],则可以向右扩展一位
            now += 1
            x += 1
            nxt.append(now)
        elif now:
            now = nxt[now-1] #缩小now,改成nxt[now-1]
        else:
            nxt.append(0)#now已经为0,无法再缩,故next[x]=0 说明第一个字符都不相同;开始比较下一位
            x+=1

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值