python--蓝桥杯--KMP算法

        解决字符串匹配问题,暴力的解法非常简单,只要枚举文本串的起始位置i,然后逐位匹配,失配时,i+1,即可。但是暴力法的时间复杂度为O(nm),当n,m比较大时,难以接受。

        下面介绍的KMP算法,时间复杂度O(n+m)。它是由Knuth、Morris、Pratt这3位科学家共同发现的,这也是KMP名字的由来。

        next数组

        在正式进入KMP算法之前,先来学习一个重要的数组。

        现在定义一个int型数组next,其中next[i]表示使子串s[0...i]的前缀s[0...k]等于后缀s[i-k...i]的最大k(注意前缀和后缀可以部分重叠,但不能是s[0...i]本身);如果找不到相等的前后缀,那么就令next[i]=-1。显然,next[i]就是所求前后缀中前缀最后一位的下标。

        next数组的求解过程如下:

        1、初始化next数组,令j=next[0]=-1。

        2、让i在1-len-1范围遍历,对每个i,执行3、4,以求解next[i]。

        3、不断令j=next[j],直至j回退为-1,或是s[i]==s[j+1]。

        4、如果s[i]==s[j+1],则next[i]=j+1;否则next[i]=j。

##输入样例
ababaab
## next数组的获取
def getNext(s):
    global Next
    ## 初始化j=next[0]=-1
    j=-1
    Next[0]=-1
    ## 求解next[1]-next[len-1]
    for i in range(1,len(s)):
        while j!=-1 and s[i]!=s[j+1]:
            ## 反复令j=Next[j]
            ## 直至j退回-1,或是s[i]==s[j+1]
            j=Next[j]
        if s[i]==s[j+1]:
            j+=1
        Next[i]=j
s=input()
Next=[-1]*len(s)
getNext(s)
print(Next)
## 输出样例
[-1, -1, 0, 1, 2, 0, 1]

        KMP算法

        在此前的基础上,下面正式进入KMP算法的讲解。读者会发现,有了上面的基础,KMP算法就是在照葫芦画瓢。从某种角度讲,next数组的含义就是当j+1位失配时,j应该回退到的位置。

        由此可以总结出KMP算法的一般思路:

        1、初始化j=-1,表示pattern当前已被匹配的最后位。

        2、让i遍历文本串text,对每个i,执行3、4来试图匹配text[i]和pattern[j+1]。

        3、不断令j=next[j],直到j回退为-1,或是text[i]==pattern[j+1]成立。

        4、如果text[i]==pattern[j+1],则令j++。如果j达到m-1,说明pattern是text的子串,返回ture。m=len(pattern)

## 输入样例
abababaac
ababaab
## next数组的获取
def getNext(s):
    global Next
    ## 初始化j=next[0]=-1
    j=-1
    Next[0]=-1
    ## 求解next[1]-next[len-1]
    for i in range(1,len(s)):
        while j!=-1 and s[i]!=s[j+1]:
            ## 反复令j=Next[j]
            ## 直至j退回-1,或是s[i]==s[j+1]
            j=Next[j]
        if s[i]==s[j+1]:
            j+=1
        Next[i]=j
## KMP算法
def KMP(text,pattern):
    ## 计算pattern的next数组
    getNext(pattern)
    ##初始化j为-1,表示当前还没有任意一位被匹配
    j=-1
    for i in range(len(text)):
        ## 不断回退,直到j==-1或text[i]==pattern[j+1]
        while j!=-1 and text[i]!=pattern[j+1]:
            j=Next[j]
        ## text[i]与pattern[j+1]匹配成功,令j加1
        if text[i]==pattern[j+1]:
            j+=1
        ## 完全适配
        if j==len(pattern)-1:
            return True
    ## 匹配失败
    return False
text=input()
pattern=input()
Next=[-1]*len(pattern)
print(KMP(text,pattern))
## 输出样例
False

        读者会发现这段代码和实现next求解的代码惊人的相似。事实上稍加思考就会发现,求解next数组的过程其实就是模式串pattern进行自我匹配的过程。

        接着考虑如何统计文本串text中模式串pattern出现的次数。主要是适配成功时,ans++,同时j如何后退,模式串pattern在哪个位置开始下一次匹配的问题。

## 输入样例
abababab
abab
## next数组的获取
def getNext(s):
    global Next
    ## 初始化j=next[0]=-1
    j=-1
    Next[0]=-1
    ## 求解next[1]-next[len-1]
    for i in range(1,len(s)):
        while j!=-1 and s[i]!=s[j+1]:
            ## 反复令j=Next[j]
            ## 直至j退回-1,或是s[i]==s[j+1]
            j=Next[j]
        if s[i]==s[j+1]:
            j+=1
        Next[i]=j
## KMP算法
def KMP(text,pattern):
    ans=0
    ## 计算pattern的next数组
    getNext(pattern)
    ##初始化j为-1,表示当前还没有任意一位被匹配
    j=-1
    for i in range(len(text)):
        ## 不断回退,直到j==-1或text[i]==pattern[j+1]
        while j!=-1 and text[i]!=pattern[j+1]:
            j=Next[j]
        ## text[i]与pattern[j+1]匹配成功,令j加1
        if text[i]==pattern[j+1]:
            j+=1
        ## 完全适配
        if j==len(pattern)-1:
            ans+=1
            j=Next[j]
    ## 匹配失败
    return ans
text=input()
pattern=input()
Next=[-1]*len(pattern)
print(KMP(text,pattern))
## 输出样例
3

        至此,大家已经可以理解next数组的求解过程和KMP算法的流程了,一般来说,上面的内容已经够日常使用了。但是,真的没有优化空间了吗?当然不是。优化过程即在求解next数组过程的基础上稍作修改即可得到。(优化,可跳过无意义的回退部分)

        优化后的next数组被称为nextval数组,它丢失了next数组的最长相等前后缀含义,却让适配时的处理达到最优,因此nextval数组的含义应该理解为当模式串pattern的i+1位发生失配时,i应该回退到的最佳位置。

## next数组优化部分
def getNextval(s):
    j=-1
    Nextval[0]=-1
    for i in range(1,len(s)):
        while j!=-1 and s[i]!=s[j+1]:
            j=Nextval[j]
        if s[i]==s[j+1]:
            j+=1
        ## 与优化前的区别所在
        if j==-1 or s[i+1]!=s[j+1]:
            Nextval[i]=j
        else:
            Nextval[i]=Nextval[j]

你可能感兴趣的:(python--蓝桥杯(算法),蓝桥杯,算法,python)