KMP算法(python实现)

1.用途

用法:查看某个词在句子中出现的位置

2.概念

假设:strData="ABCAB",下面出现的strData都是代表字符串的意思

子串:字符串中,任意连续的字符连接的串,strData的子串就有A,AB,ABC,ABCA,ABCAB,B等等

前缀:以首位字母开头的子串,strData的前缀就有A,AB,ABC,ABCA,ABCAB

真前缀:以首位字母开头且不包括末位的子串,strData的真前缀就有A,AB,ABC,ABCA

后缀:以末位字母结尾的子串,strData的后缀就有B,AB,CAB,BCAB,ABCAB

真后缀:以末位字母结尾且不包括首位的子串,strData的后缀就有B,AB,CAB,BCAB

公共前后缀:真前缀和真后缀的交集,strData的公共前后缀就有B,AB

最长公共前后缀:长度最长的公共前后缀,strData的最长公共前后缀是AB

3.获取某个词的全部前缀的最长公共前后缀的长度(最长公共前后缀&部分匹配表)

index01234
字符ABCAB
长度00012

index = 1,代表前缀子串AB,其没有最长公共前后缀,所以最长公共前后缀长度为0

index = 3,代表前缀子串ABCA,其最长公共前后缀是A,所以最长公共前后缀长度为1

假设next为词的最长公共前后缀的长度数组

假设已知next[i-1] = preLen,则字符串形式(里面的A、B、C单纯表示的是占位字符,A可能等于B,也可能不相等)如下:

字符AB...CH...MAB...Cx
index01...preLen-1preLen...i-preLen-1i-preLeni-preLen+1...i-1i

在计算next[i]的时候,利用一个特性公共前后缀代表前面部分要很后面部分一模一样,我们期望 x = strData[preLen],那样就有next[i] = next[i-1]+1=preLen+1。但有时候会出现x != strDatap[preLen],那就得从strData[i-1]的最长公共前后缀里抽取,如下面:strData="ABCABHABCABC",i=11,preLen=5

字符ABCABHABCABC
index01234567891011
长度00012012345

H != C => strData[5] != strData[11]   => strData[preLen] != strData[i] 

preLen = next[preLen]  =>  preLen = 2

字符ABCABHABCABC
index01234567891011
长度000120123453

C = C => strData[2] = strData[11]   => strData[preLen] = strData[i] 

next[i]  =  preLen + 1 => next[i] = 2 + 1 = 3

python代码:

def kmpNext(strData):
    # 字符串的长度
    strSize = len(strData)

    if strSize == 0:
        return []

    # 记录字符串最长公共前后缀&部分匹配表
    next = [0 for i in range(strSize)]

    # 遍历的索引
    strIndex = 1

    # 记录上一个最大的子串的最长公共前后缀
    preLen = 0

    # 任意一个字符的最长公共前后缀都是0
    next[0] = 0

    while strIndex < strSize:

        # 假设一个子串的最长公共前后缀的长度是Len,而strIndex代表了当前子串的长度,则该子串的最长公共前后缀是[0,Len-1]或者[i-(Len-1),i]

        # 如果当前子串string的前一个子串的最长公共前后缀的长度是preLen,此时,
        # (1)如果当前子串的string[preLen](前一个子串的最长公共字串前后缀的下一个字符) = string[i],
        #    则当前子串的最长公共字串前后缀长度为preLen+1,最长公共字串前后缀为[0,preLen]
        # (2)如果当前子串的string[preLen](前一个子串的最长公共字串前后缀的下一个字符) != string[i],
        #    1)如果此时preLen为0,说明上一个没有最长公共子串前后缀,子串的最长公共子串前后缀的长度为0
        #    2)如果此时preLen不为0,则需要利用一个特性(最大公共前后缀的公共前后缀也是子串的公共前后缀,如:ABABA的最长公共前后缀有:ABA,其中ABA的最长公共前后缀有:A)
        #      此时需要不断地拿最大公共前后缀的最大前后缀与当前字符进行比较
        if strData[strIndex] == strData[preLen]:
            preLen += 1
            next[strIndex] = preLen
            strIndex += 1
        else:
            if preLen == 0:
                next[strIndex] = 0
                strIndex += 1
            else:
                preLen = next[preLen - 1]

    return next

4.KMP算法(找到所有匹配到的字符串)

我们有一个词patternStr,一个句子matchStr,在matchStr中找到patternStr的位置

matchStr字符AHCHHHBCD
index012345678
i0
patternStr字符HHB
index012
len010
j0

如果matchStr[i] = patternStr[j],那就i +=1,j+=1,会有matchStr[i-j, j-1] = patternStr[0, j-1],这一步是为了尽可能找到匹配的字符。当 j = len(patternStr),说明匹配完成

当matchStr[i] != patternStr[j],那就有两种情况

1)j = 0,那就 i += 1。这一步说明matchStr在i位置上的字符和patternStr的首字符不匹配,此时的特点是首位都无法匹配,寻找能进行首位匹配的i

2)j != 0,那就j = next[j],利用patternStr的最长公共子串的下一位进行判断,看看是否能判断成功。此时的特点是匹配失败,选择缓存点看看能不能继续匹配,尽可能避免重新匹配,也就是j=0。

python代码:

def kmpNext(strData):
    # 字符串的长度
    strSize = len(strData)

    if strSize == 0:
        return []

    # 记录字符串最长公共前后缀&部分匹配表
    next = [0 for i in range(strSize)]

    # 遍历的索引
    strIndex = 1

    # 记录上一个最大的子串的最长公共前后缀
    preLen = 0

    # 初始化
    next[0] = 0

    while strIndex < strSize:

        # 假设一个子串的最长公共前后缀的长度是Len,而strIndex代表了当前子串的长度,则该子串的最长公共前后缀是[0,Len-1]或者[i-(Len-1),i]

        # 如果当前子串string的前一个子串的最长公共前后缀的长度是preLen,此时,
        # (1)如果当前子串的string[preLen](前一个子串的最长公共字串前后缀的下一个字符) = string[i],
        #    则当前子串的最长公共字串前后缀长度为preLen+1,最长公共字串前后缀为[0,preLen]
        # (2)如果当前子串的string[preLen](前一个子串的最长公共字串前后缀的下一个字符) != string[i],
        #    1)如果此时preLen为0,说明上一个没有最长公共子串前后缀,子串的最长公共子串前后缀的长度为0
        #    2)如果此时preLen不为0,则需要利用一个特性(最大公共前后缀的公共前后缀也是子串的公共前后缀,如:ABABA的最长公共前后缀有:ABA,其中ABA的最长公共前后缀有:A)
        #      此时需要不断地拿最大公共前后缀的最大前后缀与当前字符进行比较
        if strData[strIndex] == strData[preLen]:
            preLen += 1
            next[strIndex] = preLen
            strIndex += 1
        else:
            if preLen == 0:
                next[strIndex] = 0
                strIndex += 1
            else:
                preLen = next[preLen - 1]

    return next

def kmp(patternStr, matchStr):
    patternNext = kmpNext(patternStr)
    i = 0
    j = 0
    # 记录匹配到的词的首位,如果为空,则代表匹配不成功
    resultList = []

    while i < len(matchStr) and j < len(patternStr):
        if matchStr[i] == patternStr[j]:
            i += 1
            j += 1
            if j == len(patternStr):
                resultList.append(i-j)
                j = patternNext[j - 1]
        else:
            if j == 0:
                i += 1
            else:
                j = patternNext[j - 1]
    return resultList

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值