美文网首页
KMP字符串匹配算法的实现

KMP字符串匹配算法的实现

作者: 芒果菠萝蛋炒饭 | 来源:发表于2019-02-17 23:42 被阅读0次

    KMP字符串匹配算法的实现

    暴力查找

    • 这是最简单的一种字符串匹配算法:
    1. 使用一个指针 i 跟踪目标文本 txt, 使用指针 j 跟踪模式字符串 pat, 将 j 置为0且不断增大,直到找到一个不匹配的字符或是模式字符串结束为止。
    2. 如果没有找到匹配,那么将指针 i 向后移动一位,继续进行匹配操作
    • 这种算法的时间复杂度是O(M*N)
    def brute_force(txt, pat):
        for i in range(len(txt) - len(pat) + 1):
            for j in range(len(pat)):
                if txt[i+j] != pat[j]:
                    break
                if j == len(pat) - 1:  # 找到匹配的位置
                    return i
        return -1  # 未找到匹配的位置
    

    暴力查找算法最坏的情况需要查找 M*N 次,即对于每一个目标文本中的字符,都会匹配到模式字符串中的最后一个字符

    KMP字符串匹配算法

    • 在暴力查找算法中,当出现不匹配的情况时,会回退目标文本的指针以进行下一次匹配,但是在这种情况下,我们已经知道了一部分文本的内容(在匹配失败之前这些文本已经和模式字符串匹配成功),我们可以利用这个信息来避免文本指针 i的回退,从而减少时间复杂度

    • 例子

      暴力算法
      pattern  B A A A A A A A A
      
                                 i
                                 |   
      txt      A  B  A  A  A  A  B  A  A  A  A  A  A
                  B  A  A  A  A  A 
                     B  A  A  A  A  A 
                        B  A  A  A  A  A 
                           B  A  A  A  A  A  
                              B  A  A  A  A  A 
                                 B  A  A  A  A  A   --> 匹配成功
      

      在第5个字符(以0为起始位)匹配失败之后,暴力算法会将i回退到2,再次进行匹配。
      但是我们可以发现这里其实并不需要回退,因为 i 前面4位的字符都是 A ,均不能与 B 匹配,另外,文本字符串 txti 位的字符 B 与模式字符串 pat 的首位 B。
      因此,我们可以直接将 i 后移一位,以比较文本字符串中的下一个字符和模式字符串中的第2个字符:

                                 i->i+1
                                 |  |
      txt      A  B  A  A  A  A  B  A  A  A  A  A  A
                  B  A  A  A  A  A 
                                 B  A  A  A  A  A   --> 匹配成功
      
    • KMP 算法的主要思想就是提前判断如何重新开始匹配,而这种判断只取决于模式字符串本身,与目标文本没有关系

    • 字符串的前缀、后缀

      • 对于字符串 A 和 B ,如果存在 A = BS, 其中S是一个任意的非空字符串,那么我们就称 B 是 A 的前缀
      • 对于字符串 A 和 B ,如果存在 A = SB,其中S是一个任意的非空字符串,那么我们就称 B 是 A 的后缀
      • 例子:
        • 字符串 ABABCB
        • 前缀: {'A', 'AB', 'ABA', 'ABAB', 'ABABC'}
        • 后缀: {'B', 'CB', 'BCB', 'ABCB", 'BABCB'}
    • KMP算法是通过什么样的规则来判断如何重新匹配的?

      • 核心:部分匹配表(Partial Match Table)数组

        • 对于一个字符串 'ABABABCA', 它的PMT表如下

          char A B A B A B C A
          index 0 1 2 3 4 5 6 7
          value 0 0 1 2 3 4 0 1
        • PMT数组中值的定义:

          • 字符串的前缀集合与后缀集合的交集中最长元素的长度。
          • 例子: 对于字符串 ABAB
            • 前缀集合: {'A', 'AB', 'ABA'}
            • 后缀集合: {'B', 'AB', 'BAB'}
          • 前缀集合与后缀集合的交集是 'AB', 长度是2,因此PMT数组中的值是2
      • KMP算法是如何通过PMT表来进行重新匹配的?

        • 例子: 在文本字符串 'ABCAABABABABCABA' 中查找模式字符串 'ABABABCA'

                                i=6
                                |
              A  B  A  B  A  B  A  B  A  B  C  A  B  A
              A  B  A  B  A  B  C  A
                                |
                                j=6
          

          i 处失配,意味着 文本字符串中 i 指针之前的 PMT[j −1] 位就一定与模式字符串的第 0 位至第 PMT[j−1] 匹配, 在这里是 ABABAB。
          这个字符串的前缀集合是{'A', 'AB', 'ABA', 'ABAB', 'ABABA'}, 后缀集合是{'B', 'AB','BAB', 'ABAB', 'BABAB'}, 它们的交集是'ABAB'。
          这就说明文本字符串中, i 所在位置之前的4位和模式字符串的0到4位是相同的,因此我们可以省略掉这些字符串的比较----保持 i = 6 不动, 将 j 指向模式字符串的PMT[j-1] (此处为4)位即可

                                i=6
                                |
              A  B  A  B  A  B  A  B  A  B  C  A  B  A
                    A  B  A  B  A  B  C  A
                                |
                                j=4
          
        • 代码实现:
          在第 j 位失配时,我们实际上是取第 j-1位的PMT值,为了方便,我们可以将PMT数组向后偏移一位,得到一个数组,称为next数组:

          char A B A B A B C A
          index 0 1 2 3 4 5 6 7
          value 0 0 1 2 3 4 0 1
          next -1 0 0 1 2 3 4 0
          def kmp(txt, pat):
              i = j = 0
          
              while i < len(txt) and j < len(pat):
                  if j == -1 or txt[i] == pat[j]:  # 首位失配或者匹配成功,i、j都向后移动一位
                      i, j = i + 1, j + 1
                  else:
                      j = next[j]
          
              if j == len(pat):
                  return i - j
              else:
              return -1
          
        • next 数组的生成:

          • 求next数组的过程可以看成字符串匹配的过程:
            • 从模式字符串的第一位(不包括第0位)开始对自身进行匹配运算。 在任一位置,能匹配的最长长度就是当前位置的next值。如下图所示。
                                             pmt[0] = 0 => next[1] = 0
                                             
               i
               |
            A  B  A  B  A  B  C  A           
               A  B  A  B  A  B  C  A        pmt[1] = 0 => next[2] = 0
               |
               j
            
            
                  i
                  |
            A  B  A  B  A  B  C  A
                  A  B  A  B  A  B  C  A     pmt[2] = 1 => next[3] = 1
                  |
                  j
            
                     i
                     |
            A  B  A  B  A  B  C  A
                  A  B  A  B  A  B  C  A     pmt[3] = 2 => next[4] = 2
                     |
                     j
            
                        i
                        |
            A  B  A  B  A  B  C  A
                  A  B  A  B  A  B  C  A     pmt[4] = 3 => next[5] = 3
                        |
                        j
            
                           i
                           |
            A  B  A  B  A  B  C  A
                  A  B  A  B  A  B  C  A     pmt[5] = 4 => next[6] = 4
                           |
                           j
            
                              i
                              |
            A  B  A  B  A  B  C  A
                  A  B  A  B  A  B  C  A     pmt[6] = 0 => next[7] = 0
                              |
                              j
            
            
          • 代码实现:
            def get_next(pat)
                next = [-1]
                i, j = 0, -1
                while i < len(pat):
                    if j == -1 or pat[i] == pat[j]:
                        i, j = i+1, j+1
                        next.append(j)
                    else:
                        j = next[j]
                return next
            

    相关文章

      网友评论

          本文标题:KMP字符串匹配算法的实现

          本文链接:https://www.haomeiwen.com/subject/xtcoeqtx.html