• 第四章 字符串(1) KMP算法


    一、python中的字符串(str)

    1、python中字符串的存储形式

                                            

    2、str构造操作的实现

    O(1)时间操作:求串长、定位访问字符(python中没有字符类型,这里的访问的是只包含一个字符的字符串)

    O(n)时间操作:需要扫描整个串的内容例如:in、not  in、min、max、判断字符串类型

    二、字符串匹配

    1、朴素的串匹配算法

                                   

    步骤:在初始状态两个串的起始字符对齐(i,j = 0,0)。将模式串和目标串进行逐个字符顺序比较,直到模式串中的某个元素和目标串中不一样则将模式串右移一位(i,j = 0, j - i + 1)继续顺序比较,直到找到完整个目标串长度即可。

    def naive_matching(t,p):
        m,n = len(p),len(t)
        i,j = 0,0
        while i < m and j < n:      # i == m 说明找到了匹配
            if p[i] == t[j]:        #字符串相同考虑下一对字符
                i,j = i + 1, j + 1
            else:                 # 字符不同,则考虑t中的下一个位置
                i,j = 0,j - i + 1  # 此处在计算j时使用的是更新前的i值
        if i == m:
            b = j - i
            print(b)
            return j - i       # 无匹配返回特殊值
    
    
        return -1
    if __name__ == '__main__':
        s1 = '0000001'
        s2 = '001'
        naive_matching(s1,s2)  

    朴素的串匹配算法比较简单,容易理解,但是效率低。算法复杂度为O(mxn)。m为模式串长度,n为目标串长度。

    2、无回溯串匹配算法(KMP算法)

    2.1 KMP简述

    朴素串匹配算法没有利用字符串本身的特点,每次移位都是从头比较,没有在算法执行的过程中记录信息。KMP算法的精髓就是开发了一套分析和记录模式串信息的机制(和算法),而后借助得到的信息加速匹配。

    2.2 构建pnext表(关键)

    KMP算法关键是生成一个next表,在表中会记录在对应的模式串中的位置 i 与目标串的位置 j 匹配失败后,模式串下一步应该跳的位置。如果模式串在 j 处的字符跟目标串在 i 处的字符匹配失败后,下一步用next[ j ] 处的字符继续跟目标串 i 处的字符匹配,相当于模式串向右移动了 j - next[ j ] 位。

    构造pnext表,分为两步:(1)求模式串中各子串的前后缀公共元素的最大长度值;(2)pnext表相当于最大前后缀整体向右移动一位,然后初值赋 -1。

    首先解释下为什么要找前后缀的最大值,请看下图:

                                                                                                           

    目标串中的位置 j 之前的 i 个字符也就是模式串中的前 i 个字符,也就是说,目标串中的子串 t(j-i) ......t(j-1)就是p(0)......p(i-1)。现在需要找到一个位置 k ,下次匹配用pk与前面匹配失败的 tj 比较,也就是把模式串移动到 pk 与 tj 对准的位置,如图如果移动的正确,模式串中的子串 p(0)......p(k-1) 就应该与子串 p(i-k)......p(i-1) 匹配,而这两个子串分别为串 p(0)......p(i-1) 的长度为 k 前缀和后缀。这样,确定k的问题就变成了确定p(0)......p(i-1)的相等的前缀和后缀的长度。显然,k 值越小表示移动的越远。另一方面为了保证不遗漏可能的匹配,移动的距离应该尽可能的短,所以应该找的 k 是p(0)......p(i-1)的最长相等的前缀和后缀(不包括p(0)......p(i-1)本身,但可以是空串)的长度,这样才能保证不会跳过可能的匹配。

    下面举例说明 pnext 表的构建

    假设给定的模式串为“ABCDABD”,从左至右遍历整个模式串,其各个子串的前缀后缀分别如下表格所示:

                                

    从而有

                                 

    所以失配时,模式串向右移动的位数为 : 失配字符所在位置 - 失配字符对应的pnext值

    2.3 代码递推pnext值

    如下图所示,假设现在要对子串p(0)......p(i-1) p( i )(也就是对位置 i)递推计算最长相等前后缀的长度,这时对 pnext [ i - 1] 已经计算出结果为 k - 1,比较 p(i) 与 p(k),有两种情况:

                                       

    (1)如果 p(i) = p (k),那么对于 i 的最长相等前后缀的长度,比对 i - 1 的最长相等前后缀的长度多 1 ,由此应该pnext[ i ] 设置为 k ,然后考虑下一个字符。

    (2)否则就应该把 p(0)......p(k-1)的最长相等前缀移过来继续检查。

    注意,第二种情况并没有设置,只是继续检查。

    已知pnext[ 0 ] = -1 和直至 pnext[ i - 1] 的已有值,求pnext[ i ]的算法:

    (1)假设pnext[ i - 1] = k - 1,如果p(i)= p(k),则pnext[ i ] = k,将 i 加 1 后继续递推(循环)。

    (2)如果 p(i)!= p(k),就将 k 设置为 pnext[ k ] 的值(将 k 设置为 pnext[ k ],也就是转区考虑前一个更短的保证匹配的前缀,可以基于它继续检查)。

    (3)如果 k = -1,(这个值一定是由于第2步而来自pnext),那么p(0)......p(i)的最长相等前后缀的长度就是0,设置pnext[ i ] = 0,将 i 值加 1 后继续递推。

    程序如下 gen_pnext(p) 为pnext表构建程序,matching_KMP(t,p,pnext) 为 KMP算法整体程序。

    def gen_pnext(p):
        #生成针对p中各位置i的下一个检查位置表,用于KMP算法,已知pnext[0]=-1和pnext[i-1]直至的已有值求的算法,为了创造这样的条件,故在开始时设置所有的初始值为-1
        i,k,m = 0, -1, len(p)
        pnext = [-1] * m        # 初始数组元素全为-1
        while i < m - 1 :       # 生成下一个pnext元素值
            if k == -1 or p[i] == p[k]:
                i,k = i + 1,k + 1
                pnext[i] = k    # 设置pnext元素
            else:
                k = pnext[k]    # 退到更短相同前缀
        print(pnext)
        return pnext
    
    def matching_KMP(t,p,pnext):
        '''
        KMP 串匹配,主函数
        :param t: 目标串
        :param p: 模式串
        :param pnext: next 表
        :return:
        '''
        j,i = 0,0
        n,m = len(t),len(p)
        while j < n and i < m:            # i==m说明找到了匹配
            if i == -1 or t[j] == p[i]:   #考虑p中的下一个字符
                j,i = j + 1,i +1
            else:                #失败!,考虑pnext决定的下一个字符
                i = pnext[i]
        if i == m:
            print(j - i)
            return j - i    # 找到匹配返回其下标
    
        return -1    # 无匹配返回特殊值
    
    if __name__ == '__main__':
        #gen_pnext("abbcabcaabbcaa")
        matching_KMP("zzzabbcabcaabbcaa","abbcabcaabbcaa",gen_pnext("abbcabcaabbcaa"))  

    2.3 KMP算法的复杂度

    一次KMP算法的完整执行包括构造pnext表和实际匹配,设模式串和目标串长度分别为m和n,KMP算法的时间复杂度为O(m + n),由于 m << n,因此该算法的时间复杂度为O(n)。

     

    既然无论如何时间都会过去,为什么不选择做些有意义的事情呢
  • 相关阅读:
    iOS 开发之粒子效果
    ios 中使用SBJson拼接和解析json
    iphone document 图片存储和读取
    ios项目绕过证书访问https程序
    我应该直接学Swift还是Objective-C?
    iOS中使用 Reachability 检测网络
    iOS App性能优化
    iOS中的堆(heap)和栈(stack)的理解
    iOS中引用计数内存管理机制分析
    iOS多线程编程Part 3/3
  • 原文地址:https://www.cnblogs.com/xiaodongsuibi/p/8386907.html
Copyright © 2020-2023  润新知