字符串匹配理解

    对我一个菜鸟而言,理解字符串匹配优化算法着实有些难,看了很多他人的理解。
自己终于稍稍有些理解。赶紧自己总结了一下
对于字符串匹配一直是一个热点,首先我们可以使用暴力法进行比较,当找到了完全
匹配的序列,则返回对应序列的首字母位置。

1.暴力法
设定两个索引指针i,j分别初始为0,若s[i]==p[j],则同时加1,若是s[i]!=p[j]
则,i后退匹配时的元素加1,j=0.重复此操作,直到j==p的长度。则匹配成功。
2.rabinkarp算法
此算法其实是将源字符串每次扫描时的n个字符串进行计算hash。
这样不好表述。比如我们源字符串长度为10,匹配字符串为4.
那我们首先将源字符串前0-3中的四个字符进行hash计算。然后整体对1-4的四个字符进行hash计算。直到将源字符串所有长度为4的可能字符串的hash值计算完毕
继续将匹配字符串计算出hash值。
hash值计算方法:
假设p=’abcd’
hash=’a’*31^3+’b’*31^2+’c’*31^1+’d’
=(((0+’a’)*31+b)*31+’c’)*31+’d’
#此时的字母的值其实是对应的二进制编码ord(c)
注:hash值运算其实就是将一个字符串通过某种算法变成一个数字,这个数字在很大范围内几乎不会重复,但凡事总有例外。重复时的解决办法后面讲解。
当然,对于字符串进行hash计算的预处理中,源字符串计算过程中同样达到了O(m*n)的时间复杂度。因为此时源字符每次处理的时间为O(n),共计算大概m次
这样的复杂度打不到我们的优化目的。
大牛便再想想,能不能让hash值计算达到O(1)。
于是提出了一个叫滚动hash。

滚动hash
其实就是在计算第一个hash源字符串之后,向前推进一位的同时,扣除最后一位,得到新的字符串 hash值,这样在预处理字符串的hash值只需要O(m + n)
m是源字符串所花费的时间,n是匹配字符串所花费的时间。最后进行m次比较hash值则可以找出所有匹配的字符串位置
总的时间复杂度达到O(2m+n)。达到了优化的目的
下面是代码rabinkarp的代码

def get_hash(s,n):
    res=[]   #存储hash值
    temp=ord(s[0])
    seed=31
    for i in range(1,n):
        temp=(temp)*seed+ord(s[i])      #存储第一个n长字符串的hash值
    res.append(temp)
    for i in range(n,len(s)): #进行滚动hash。每前进一个字符,就扣掉扣掉最后一个字符。
        #即等于加上新字符并乘以seed,然后减去老字符*seed**n
        res.append(((res[i-n]*seed)+ord(s[i])-ord(s[i-n])*seed**n))
    print(res)
    return res
def match(hash1,hash2,n,s,p):
    for i,h in enumerate(hash1):
        if h==hash2:
            k=0
            for j in range(i,i+n):
                if s[j]!=p[k]:
                    return -1
                else:
                    return i
    return -1


if __name__ == '__main__':
    s='ABABABA'
    p='ABA'
    hash1=get_hash(s,len(p))
    hash2=get_hash(p,len(p))[0]
    index=match(hash1,hash2,len(p),s,p)
    print(index)
#

3:kmp算法
kmp算法大家在网上也能看到很多多牛的解法,救我自己理解来说,kmp算法做的就是和匹配字符串匹配失败时,给匹配字符串留一些字符,因为我们之前辛辛苦苦比较的舍不得一下子又从头再来。
后来大牛们各种找规律,发现每次比较时,i指针不用后退,而j指针可以后退到某一个位置。
这个位置是有一定规律的。具体规律和匹配的字符串p有关。
这个数组的长度恰好等于匹配字符串p的长度。我们把这个数组定义为next[],涵义是每当s[i]!=p[j]时,j指针应该回退到p字串中的next[j]位置(下标)。
所以求next[j]就是我们的一大关键。

next[]数组求法
首先我们要分清楚到底next[]数组的涵义。
并且我们可以发现next[j]=k代表了0-j中有前k个字符等于后k个字符。
即k前缀等于k个后缀(p[:k]==p[-k:])。
而我们求出next[j]时,需要求next[j+1]时,分两种情况:

首先next[0]=-1 因为前面没有字符。
1.当k==-1 or  p[k]==p[j]时。(k=next[j])
        next[j+1]=next[j]+1
2.当p[k]!=p[j] 则 k=next[k] 继续判断这两步。直到得出值或者k==-1

求出next数组,整个kmp算法的精髓也就出来了。
    那么剩下的就是使用了next[]进行匹配。整个题目的代码如下。
def match(s,p):
    i=0
    j=0
    while i<len(s) and j<len(p):
        if j<0 or s[i] == p[j]:
            j += 1
            i += 1
        else:
            j=next[j]
    return i-j if j == len(p) else -1
def get_next(p):      #得到next数组。
    #next数组定义为当j和i不匹配时,j应该在哪个位置和i重新进行比较。
    next_local = [0]*len(p)
    next_local[0] = -1
    i = 0
    k = next_local[i]
    while i < len(p)-1:
        if k == -1 or p[i] == p[k]:        #假设已经知道了next[i]数组,如果知道了p[j]==p[k]。那么前缀的长度会加1
            ## 注:next数组会刨去当前的元素。
            next_local[i+1] = k+1
            k += 1
            i += 1
        else:
            k = next_local[k]
    return next_local
if __name__  ==  '__main__' :
    s = 'sdfsdfdsfbababb'
    p = 'bababb'
    next = get_next(p)
    print(next)
    print(match(s,p))

因小生愚笨,且是新手,只能理解到这个地步,若有错误,请直接指出来。谢谢

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值