字符串查找匹配--kmp算法

部分匹配表

KMP的关键是部分匹配表。理解KMP的主要障碍是没有完全掌握部分匹配表中的值到底意味着什么。试着用最简单的话来解释它们。

这是“abababca”模式的部分匹配表:

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 |

现在,为了谈论其含义,我们需要了解正确的前缀和正确的后缀。

正确的前缀:

字符串中的所有字符,其中一个或多个字符被截断。
“S”,“Sn”,“Sna”和“Snap”都是“Snape”的正确前缀。
截取的范围是从字符串的第一个字符到倒数第二个字符。

正确的后缀:

字符串中的所有字符,一个或多个字符在开头处截断。
“agrid”,“grid”,“rid”,“id”和“d”都是“Hagrid”的正确后缀。
截取的范围是从字符串的第二个字符到倒数第一个字符。

考虑到这一点,我现在可以给出部分匹配表中值的一句话含义:

(子)模式中与同一(子)模式中的正确后缀匹配的最长正确前缀的长度。

现在来分析“abababca”这个模式串:

对“a”分析,只有一个字符串,没有前缀和后缀,故值为0。

对“ab”分析,有一个正确的前缀(“a”)和一个正确的后缀(“b”),前缀和后缀不匹配,故值为0。

对“aba”分析,有两个正确的前缀(“a”和“ab”)和两个正确的后缀(“a”和“ba”)。正确的前缀“ab”与两个正确的后缀中的任何一个都不匹配。但是,正确的前缀“a”与正确的后缀“a”匹配。因此,在这种情况下,匹配正确后缀的最长正确前缀的长度是1。

对四个字符(“abab”)分析,有三个正确的前缀(“a”,“ab”和“aba”)和三个正确的后缀(“b”,“ab”和“bab”)。“ab”在两者中,并且是两个字符长,因此值为2。

对“ababa”分析,有四个正确的前缀(“a”,“ab”,“aba”和“abab”)和四个正确的后缀(“a”,“ba”,“aba”和“baba”)。现在,有两个匹配:“a”和“aba”都是正确的前缀和正确的后缀。由于“aba”比“a”长,所以它获胜,因此值为3。

对“abababc”分析。即使没有列举所有正确的前缀和后缀,显然也不会有任何匹配; 所有后缀都以字母“c”结尾,并且没有前缀与之匹配,因此值为0。

对“abababca”分析,由于它们都以“a”开头和结尾,我们知道它的值至少为1.但是,它就是它的结束点; 在长度为2或更高时,所有后缀都包含ac,而只有最长的前缀(“abababc”)包含“c”。这个七个字符的前缀与七个字符的后缀(“bababca”)不匹配,因此值为1。

如何使用部分匹配表

当我们找到部分匹配时,我们可以使用部分匹配表中的值来跳过(而不是重做不必要的旧比较)。该公式的工作方式如下:

如果找到长度为partial_match_length的部分匹配table[partial_match_length] > 1,我们可以跳过前面的partial_match_length - table[partial_match_length - 1]字符。

假设我们将“abababca”模式与“bacbababaabcbab”相匹配。这里是我们的部分匹配表,以便于参考:

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 |

我们第一次获得部分匹配是在这里:

bacbababaabcbab
 |
 abababca

这是partial_match_length为1. table[partial_match_length - 1](或table[0])的值为0,因此我们不会先跳过任何一个。我们得到的下一个部分匹配是:

bacbababaabcbab
    |||||
    abababca

这是partial_match_length为5. table[partial_match_length - 1](或table[4])的值为3.这意味着我们可以跳过前面partial_match_length - table[partial_match_length - 1](或5 - table[4]或5 - 3或2)字符:

// x denotes a skip

bacbababaabcbab
    xx|||
      abababca

这是partial_match_length 3. table[partial_match_length - 1](或table[2])的值是1.这意味着我们可以跳过前面partial_match_length - table[partial_match_length - 1](或3 - table[2]或3 - 1或2)字符:

// x denotes a skip

bacbababaabcbab
      xx|
        abababca

代码实现


def makeNext(pattern):
    """
    求出模式串的最大前后缀长度数组
    :param pattern: 模式串
    :return:
    """
    # 最大前后缀长度
    k = 0
    # 模版字符串长度
    m = len(pattern)
    next = [0] * (m-1)
    # 模版字符串的第一个字符的最大前后缀长度为0
    next.append(0)

    # 从第二个字符开始,依次计算每一个字符对应的next值
    for q in range(1, m):
        # 求出P[0]···P[q]的最大的相同的前后缀长度k
        while k > 0 and pattern[q] != pattern[k]:
            k = next[k-1]
        # 如果相等,那么最大相同前后缀长度加1
        if pattern[q] == pattern[k]:
            k = k + 1
        next[q] = k

    return next

def kmp(target, pattern):
    """
    在目标串里查找模式串,成功查找后返回目标串中模式串出现位置的第一个下标
    :param target: 目标串
    :param pattern: 模式串
    :return:
    """
    # 目标串下标,不回溯
    q = 0
    # 目标串的长度
    n = len(target)
    # 模式串的长度
    m = len(pattern)
    # 求取模式串的匹配串数组
    next = makeNext(pattern)
    for i in range(1, n):
        while q > 0 and pattern[q] != target[i]:
            q = next[q-1]
        # 模式串中的字符与目标串的字符相等,q向后移一位
        if pattern[q] == target[i]:
            q = q + 1
        #
        if q == m:
            return i - m + 1



if __name__ == '__main__':
    t = 'ACBDCBADAABCDABCA'
    p = 'ABCDABC'
    print(t)
    print(p)
    # next = makeNext(p)
    result = kmp(t, p)
    print(result)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容