• KMP匹配算法


    KMP算法(Knuth–Morris–Pratt Algorithm),Knuth就是高德纳。
    KMP算法是从朴素匹配算法改进而来:就是穷搜匹配。

    朴素匹配算法:

    原串:ABCDE 模式串:ACD
    1. 将原串和模式串左对齐,然后一位一位比较,直到有一个字符不匹配
    这里写图片描述
    2. 发现第二位的B和C不匹配,模式串右移一位
    这里写图片描述
    3. 重复这个流程,直到找到完全匹配的子串或者匹配失败。

    朴素匹配算法的不足:

    就是不能够很直观的进行有移动的大跨步,也就是明明是不匹配的,但是不能跳过去,不够聪明。
    例子:ABCDEABCDF 和 ABCDF。遇到E和F时候冲突,
    这里写图片描述
    此时明显人能直到应该直接调到E后面的A开始啊。
    这里写图片描述

    那是不是跳过的长度就是前面相同部分的长度呢?

    其实不是这样的。
    原串为ABCDABCDABF,模式串为ABCDABF,直接跳过相同部分就会遗漏匹配的串,所以跳过的长度并不是前面完全匹配的部分
    KMP5.png

    那是不是跳过的长度如何定下来?

    其实就是人之所以强大是可以马上发现下一个匹配开始的位置,比如模式串是A开头,那么匹配串肯定每次都是找到原串下一个A开始的继续匹配。机器不知道下一个究竟在哪,怎么跳过去合适。
    也就是机器怎么靠近人的眼睛呢。直接发现下一个合理的开始位置呢,也就是如何跳到那个合理位置呢?这就是KMP算法解决的.

    KMP算法

    KMP算法需要对模式串进行预处理。
    把可以跳过的长度一般存储在模式串的partial match table中。
    例如:ABCDABF的partial match table如下:
    这里写图片描述

    可以跳过的长度 = 当前模式串已匹配长度 - 模式串最后一个字母在partial match table中的值。例如:

    这里写图片描述
    当发现C和F不匹配时,根据公式,当前已匹配串ABCDAB长度为6, 最后一个字母B在partial match table中的对应值为2,所以可以跳过的长度 = 6 - 2 = 4,即:
    这里写图片描述

    【partial match table是如何构建的?】

    每个位置i记录的其实就是从0到i的子串中,同时出现在子串前缀和后缀中的最大长度。还是以上面那个例子为例:

    • A没有前缀或后缀,所以长度为0
    • AB前缀为A,后缀为B,没有相同部分,长度为0
    • ABC前缀为A、AB,后缀为BC、C,没有相同部分,长度为0
    • ABCD前缀为A、AB、ABC,后缀为BCD、CD、D,没有相同部分,长度为0
    • ABCDA前缀为A、AB、ABC、ABCD,后缀为BCDA、CDA、DA、A,相同部分为A,长度为1
    • ABCDAB前缀为A、AB、ABC、ABCD、ABCDA,后缀为BCDAB、CDAB、DAB、AB、B,相同部分为AB,长度为2
    • ABCDABF前缀为A、AB、ABC、ABCD、ABCDA、ABCDAB,后缀为BCDABF、BCDAB、CDAB、DAB、AB、B,没有相同部分,长度为0

    demo

    '''
    Knuth-Morris-Pratt algorithm
    '''
    
    def getPrefix(pattern):
        '''
        @param pattern: pattern string.
        @return: prefix array.
        '''
        length = len(pattern)
        prefix = [0] * length
        for i in range(1, length):
            k = prefix[i-1]
            while k > 0 and pattern[i] != pattern[k]:
                k = prefix[k-1]
            if pattern[i] == pattern[k]:
                prefix[i] = k + 1
    
        return prefix
    
    def KMP(src, pattern):
        prefix = getPrefix(pattern)
        print(prefix)
        i = j = 0
        src_len = len(src)
        pattern_len = len(pattern)
    
        if src_len < pattern_len:
            return -1
    
        while i < src_len:
            while src[i] == pattern[j]:
                if j == pattern_len - 1:
                    return i - j
                else:
                    i += 1
                    j += 1
            if j > 0:
                j = prefix[j-1]
            else:
                i += 1
        return -1
    
    src = 'bbc abcdab abcdabcdabde'
    pattern = input('pattern: ')#'abcdabd'
    #print(KMP(src, pattern))
    print(getPrefix(pattern))

    【延展】
    这类问题的思路是解决重复,有点像qucik sort消除逆序对。

  • 相关阅读:
    Windows 系统共享文件扫描
    工业以太网EtherNet/IP协议安全分析整理
    说说搜索,你觉得自己的信息有多安全
    java网络编程
    攻击WordPress和其他程序
    ZenMap扫描笔记
    WireShark Wifi认证数据包分析(论文idea)
    linux 基础知识(三)
    unity 3D 学习笔记
    WPA-PSK无线网络密码破解原理
  • 原文地址:https://www.cnblogs.com/itrena/p/7434099.html
Copyright © 2020-2023  润新知