zoukankan      html  css  js  c++  java
  • 用PYTHON实现KMP字符串匹配

    本文只讨论KMP的实现,原理可从以下网站自行阅读理解,写的非常好

    http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html

    阮一峰大神写的


    理解后可知实现KMP最重要的是部分匹配表PML(Partial match list)的构建及调用

    从上面的链接引用一下:

    "部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例,

      - "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;

      - "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

    PML表的功能应包含:

    标出对应单个字符所在位置的最大部分匹配值PMV(Partial match value)

    本质上就是动态规划的实现,因此设一个DP表,每一个位置代表对应INDEX字符的部分匹配值

    构建思路:

    DP[i]的值实际上与DP[i-1]的值有关联,匹配时可能出现下面几种情况:

    1.如果DP[i-1]存在值

    说明与前面的字符串已经开始匹配了,

    • 如果对应字符s[i]等于下一个应该轮到匹配的前面的字符,则PMV继续增加一个单位

      如:将ABCAB 中的第二个B与第一个B进行匹配。

      则DP[i] = DP[i-1]+1 【情况一】

    • 如果对应字符s[1]不等于下一个应该轮到匹配的前面的字符,此时又有两个情况分支:

        1.如果s[i]等于字符串的第一个字符,则又可以在此开始新的一轮匹配,

         如:ABCAA,在这里第三个A不等于B,但是它等于第一个A,所以DP[i]仍为1 【情况二】

        2.如果s[i]不等于字符串的第一个字符,那DP[i]就为0 【情况三】

    2.如果DP[i-1]不存在值

    说明之前一个单位也没有匹配的情况,

    • 如果对应字符s[i]等于字符串第一个字符,则DP[i] = 1,在此开始新的匹配 【情况四】
    • 如果对应字符s[i]不等于字符串第一个字符,则DP[i] = 0 【情况五】

    仔细一看,【情况二三】和【情况四五】其实可以放在一起讨论:

    【情况一】的实现条件整合一下:

    条件一:DP[i-1]存在值

    条件二:s[i] 等于轮到匹配的前字符

    实现上述两个条件则DP[i] = DP[i-1]+1

    不符合上述条件的直接分类到【情况二三四五】:

    • 如果对应字符s[i]等于字符串第一个字符,则DP[i] = 1,在此开始新的匹配 【情况二/四】
    • 如果对应字符s[i]不等于字符串第一个字符,则DP[i] = 0 【情况三/五】

    上PML的构建代码:

    def Partial_match_list(lst): #建立部分匹配表
            dp = [-1] + [0 for i in range(len(lst))] #留个-1,避免范围溢出
            r = 0 #记录连续值
            for i in range(1,len(dp)):#跳过0
                if dp[i-1] and lst[i-1] == lst[r]: #判断1:如果上一个点存在值 #判断2:如果str当前值与记录值相同
                    dp[i] = dp[i-1]+1 #条件通过:当前dp值为上个dp值+1
                    r = dp[i] #记录值与DP值相同
                else:#如果不符合上述条件  
                    dp[i] = 1 if lst[i-1] == lst[0] else 0 #如果str当前值与首值相等,则当前dp为1,否则为0
                    r = 1 if dp[i] else 0 #如果当前dp为1,则记录值也为1,否则为0
            return dp[1:] #由于0号是建表初期拿来避免范围溢出的,后续用不到,所以不输出


    有了PML后,KMP的实现就相对简单很多了

    def KMP(text, match): #text为原文,match为匹配文本
            text, match = s, m
            if len(m) > len(s): #如果查询字符大于文本,直接退出
                return -1
            dp = Partial_match_list(m) #使用先前代码制作PML
            index,dp_index = 0,0 #设定文本index与dp_index:文本index用于标注检测中的文本字符位置;dp_index用于标注检测中的匹配字符位置
            l,dp_l = len(s), len(dp) #计算文本长度与dp长度:文本长度用于检测循环是否到末尾;dp长度用于检测是否达成检索目标
            while index <= l-1: #当文本index仍在合理值内,便继续循环
                if s[index-dp_index] == m[0]:# s[index-dp_index]能准确指出当前匹配部分文本字符串的头号字符;如果其与匹配字符串的头号字符相等,则继续, 否则直接增加index去对照下一块文本
                    while index <= l-1 and s[index] == m[dp_index]: #当文本index仍在合理值内 且 当前文本字符与对应匹配字符相等,进入循环
                        if dp_index == dp_l-1: # 如果dp_index达到了最大理论长度,说明匹配字符全部通过匹配,程序完成
                            return index - dp_index #程序完成,进行反馈,返回发生匹配时最开始的序列
                        index += 1 #如果还未匹配完毕,则增加index与dp_index,继续匹配
                        dp_index += 1
                    else:
                        dp_part = dp[:dp_index] #当匹配出现不相等时,提取dp_part,其为目前已通过匹配的部分匹配字符串
                        dp_index = max(dp_part) #将dp_index重新定位到目前dp_part中的最大值,这是下一步要比较的dp_index
                else:
                    index += 1 #因为两组首字符不等,直接增加index比较下一组。
            return -1 #原文中并没有出现匹配字符 返回-1

  • 相关阅读:
    《SPFA算法的优化及应用》——姜碧野(学习笔记)
    hdu 4691 Front compression
    hdu 4690 EBCDIC
    UVA 11478 Halum(用bellman-ford解差分约束)
    UVA 11090 Going in Cycle!!(二分答案+判负环)
    UVA 10537 The Toll! Revisited uva1027 Toll(最短路+数学坑)
    hdu 4674 Trip Advisor(缩点+倍增lca)
    canny算子原理
    二值图像连通区域标记
    C++全局变量的声明和定义
  • 原文地址:https://www.cnblogs.com/phinza/p/11751114.html
Copyright © 2011-2022 走看看