美文网首页iOS CraziesiOS Developer
KMP(一) 模式匹配算法推导 --《部分匹配表》

KMP(一) 模式匹配算法推导 --《部分匹配表》

作者: hehtao | 来源:发表于2017-08-31 15:48 被阅读539次

    概述:本文主要在理论层面上分析KMP的基本实现原理以及《部分匹配表》推导过程;不涉及代码实现;如果您对KMP的实现代码(OC)实现感兴趣,可参考:
    KMP(一) 模式匹配算法推导 --《部分匹配表》
    KMP(二) 模式匹配算法实现
    KMP(三) 字符串快速匹配示例


    一: KMP主要解决的问题:

    KMP是三位大牛:D.E.Knuth、J.H.Morris和V.R.Pratt同时发现的。其中第一位就是《计算机程序设计艺术》的作者!!

    KMP算法要解决的问题就是在字符串(也叫主串,下文统一称"P")中快速高效匹配子串(下文统一称"S")并确定S在P中位置的问题。说简单点就是我们平时常说的关键字(词)搜索。

    二. 字串朴素匹配算法实现:

    看一个朴素匹配算法示例:
    主串: P="BBC ABCDAB ABCDABCDABDE" 长度为Length_p
    字串: S = "ABCDABD" 长度为Length_s



    首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一个字符与搜索词"ABCDABD"的第一个字符,进行比较。因为B与A不匹配,所以搜索词后移一位。



    因为B与A不匹配,搜索词再往后移。



    就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止。



    接着比较字符串和搜索词的下一个字符,还是相同。


    直到字符串有一个字符,与搜索词对应的字符不相同为止。


    这时,最自然的反应是,将搜索词整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"搜索位置"移到已经比较过的位置,重比一遍,很明显P的位置在回溯。分析朴素匹配算法时间复杂度为:
    O((Length_p - Length_s + 1) * Length_s)

    为方便后续说明,我们来约定一组表达规则:

    主串 P 和字串 S 中有以下表达,示例如:
    P[0] = "B"
    P[4] = "A"
    P[0~5]="BBC AB"
    S[0] = "A"
    S[2]="C"
    S[1~3]="BCD"

    我们观察一下此时字串 S 和 主串P:
    以下重要:
    当S[0~5] = P[4~9] 并且 S[6] != P[10]时,S[0~3] 内没有重复的字符,而切S[0~3] =P[4~7],所以在进行下一次比较时,我们可以直接将字串S移动至主串P的P[8] 位置开始比较,同时保持P的上一次查找位置不变(P[10]), 而不是像上图一样从P[5]开始循环比较,因为此时的P[5~8] 不可能与S(主要看S[0~3])匹配 ;这样效率会大大提升;接下来看KMP是如何做的;

    三. KMP算法示例分析:

    KMP核心原则: 保持主串P上次查找位置(index_p)不回溯,移动字串S到合适的位置(index_s),下次比较起始位置分别为P[index_p]和S[index_s],不在比较P[0~index_p] 和 S[0~index_s],从而达到简化匹配的计算过程;具体示例如下:

    这里要引入一个《部分匹配表》概念;部分匹配表推导过程稍后做解释;我们要做的先学会如何利用《部分匹配表》去实现KMP模式匹配,从而理解KMP的匹配过程;字串移位计算公式如下:

    移动位数(S) = 已匹配的字符数 - 对应的部分匹配值

    接着上述利用《部分匹配表》匹配分析:


    7.

    已知空格与D不匹配时,前面6个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应的"部分匹配值"为2,照上面的公式算出向后移动的位数:
    因为 6 - 2 等于4,所以将S向后移动4位。


    8.

    因为空格与C不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2("AB"),对应B的"部分匹配值"为0。所以,移动位数 = 2 - 0,结果为 2,于是将搜索词向后移2位。


    9.

    因为空格与A不匹配,而S已经到达最左端,故P继续后移一位。


    10.

    逐位比较,直到发现C与D不匹配。于是,移动位数 = 6 - 2,继续将搜索词向后移动4位。


    11.

    逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。如果还要继续搜索(即找出全部匹配),移动位数 = 7 - 0,再将搜索词向后移动7位,这里就不再重复了。

    四. 《部分匹配表》计算过程

    首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。注意:此处有个组合的概念,表达不同于KMP 算法实现中的前缀和后缀


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

    1. "A"的前缀和后缀都为空集,共有元素的长度为0;
    2. "AB"的前缀为[A],后缀为[B],共有元素的长度为0;
    3. "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
    4. "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
    5. "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;
    6. "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;
    7. "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

    相关文章

      网友评论

      本文标题:KMP(一) 模式匹配算法推导 --《部分匹配表》

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