《柔性字符串匹配》读书笔记(1)之--KMP算法(单模式串匹配、前缀匹配)

                                                                                                    ——by m4trix

《柔性字符串匹配》: 《Flexible Pattern Matching in Strings》
KMP算法: Knuth-Morris-Pratt algorithm
Knuth is Donald Knuth, you know the guy I mean.  


所谓字符串匹配:给出两个字符串A,B,回答B串是否是A串的子串(A串是否包含B串)。这里称A为文本,B为模式,即要回答模式B是否为文本A的子串。

有:
文本 A = t1t 2t3...t n
模式 B = p1p 2p3...p m
传统的字符串匹配算法:(算法时间复杂度O((n-m+1)*m))

        把文本A中位移分别0,1,...n-m时的文本(t1 t23...tm,  t 234...t m+1,  t 345...t m+2,  ...)依次跟模式B进行比较,查找匹配与否。

        这种算法没有对已匹配过的信息加以利用,KMP算法就是在充分利用已匹配信息的基础上,来避免一些明显的不合理的移位(每次当t xx+1x+2...t x+m-1与模式B不匹配时,需要对文本A后移一位t x+1x+2x+3...t x+m-2来继续进行对比,其实有的时候可以一次移z(z>1)位,这样子效率更优)。那么问题来了:KMP是怎么来对已匹配信息加以利用的呢,每次不匹配了需要对文本移位再比较时,移多少位呢,怎么确定:

KMP算法的思想:(算法最坏时间复杂度O(n),平均时间复杂度O(n))
一些概念:
        部分匹配表(Partial Match Table):所谓部分匹配表,即模式B中当匹配到第i个字符时,在p1 2p3 ...p i中,既是该串的前缀、同时又是该串的后缀的最长字符串的长度与索引i的一个映射关系表。
        如有模式串:"abababca"
        则有部分匹配表:
              
        如何计算得来:
                - "a"的前缀和后缀都为空集,因此公共元素的最大长度为0;
                - "ab"有前缀["a"], 后缀["b"], 公共元素的最大长度为0;
                - "aba"有前缀["a", "ab"], 后缀["ba", "a"], 公共元素"a"的最大长度为1;
                - "abab"有前缀["a", "ab", "aba"], 后缀["bab", "ab", "b"], 公共元素"ab"的最大长度为2;
                - "ababa"有前缀["a", "ab", "aba", "abab"], 后缀["baba", "aba", "ba", "a"], 公共元素"a", "aba"的最大长度为3;
                - "ababab"有前缀["a", "ab", "aba", "abab", "ababa"], 后缀["babab", "abab", "bab", "ab", "b"], 公共元素"ab", "abab"的最大长度为4;
                - "abababc"有前缀["a", "ab", "aba", "abab", "ababa", "ababab"], 后缀["bababc", "ababc", "babc", "abc", "bc", "c"], 公共元素的最大长度为0;
                - "abababca"有前缀["a", "ab", "aba", "abab", "ababa", "ababab", "abababc"], 后缀["bababca", "ababca", "babca", "abca", "bca", "ca", "a"], 公共元素"a"的长度为1;
        部分匹配长度(partial_match_length):所谓部分匹配长度,即文本A中的子串与模式B中的前i个字符相匹配,第i+1个字符不匹配,则此时的部分匹配长度为i.

有了部分匹配表和部分匹配长度,KMP算法对其怎么加以利用:
当文本A中的子串
部分匹配模式B时,有部分匹配长度:partial_match_length, 部分匹配表PMTable, 则当PMTable[partial_match_length] > 1, 则文本A向前位移 partial_match_length - PMTable[partial_match_length - 1]长度,继续用子串与模式B来匹配,依次循环,直到匹配上或者直至文本A走完为止。 

综上,KMP分两步来完成:
1、对模式串进行预处理,计算出部分匹配表;
2、依据Partial Match Table,对文本进行跳跃式位移来拿文本子串和模式进行匹配。

示例:
文本A:"bacbababaabcbab"
模式B:"abababca"

1、

如上图:
没有部分匹配中,文本A向前移1位;

2、

如上图:
部分匹配,有partial_match_length = 1,
根据:
              if  (PMTable[partial_match_length] > 1) {
                     文本A向前移partial_match_length - PMTable[partial_match_length - 1]长度;
              } else {
                      文本A向前移 1 位;
              }
PMTable[1] = 0 < 1,
文本A向前移1位;

3、

如上图:
没有部分匹配中,文本A向前移1位;

4、

如上图:
没有部分匹配中,文本A向前移1位;

5、

如上图:
部分匹配,有partial_match_length = 5,
PMTable[5] = 4 > 1,  partial_match_length - PMTable[partial_match_length - 1] = 5 -  PMTable[5 - 1] = 5 - 3 = 2,
文本A向前移2位;

6、

如上图:
部分匹配,有partial_match_length = 3,
PMTable[3] = 2 > 1,  partial_match_length - PMTable[partial_match_length - 1] = 3 -  PMTable[3 - 1] =3 - 1 = 2,
文本A向前移2位;

7、

如上图:
模式的长度已经超出了剩余的子文本串的长度了,因此匹配结束,没有匹配中。


C代码实现:
<span style="font-size:18px;">/*
 * input: x is the pattern string
 * input: m is the length of pattern string
 * output: KMPNext is the Partial Match Table
 */
void preKMP(char *x, int m, int KMPNext[])
{
    int i, j;

    i = 0;
    j = KMPNext[0] = -1;

    while (i < m) {
        while (j > -1 && x[i] != x[j]) {
            j = KMPNext[j];
        }
        i++;
        j++;
        if (x[i] == x[j]) {
            KMPNext[i] = KMPNext[j];
        } else {
            KMPNext[i] = j;
        }
    }
}


void KMP(char *x, int m, char *y, int n)
{
    int i, j, KMPNext[XSIZE];

    /* Preprocessing */
    preKMP(x, m, KMPNext);

    /* Searching */
    i = j = 0;
    while (j < n) {
        while (i > -1 && x[i] != y[j]) {
            i = KMPNext[i];
        }
        i++;
        j++;
        if (i >= m) {
            //OUTPUT(j - i);
            i = KMPNext[i];
        }
    }
}</span>

KMP算法的适用场景:
因为KMP算法是对模式串进行预处理,因此该算法非常适合求解这样的问题:
给定一个模式串B,和一群文本A串,问B是哪些A串的子串。


Refer:

"KMP算法详解"

"Knuth-Morris-Pratt algorithm description and C code by Christian Charras and Thierry Lecroq"
http://www-igm.univ-mlv.fr/~lecroq/string/node8.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值