KMP(字符串匹配)


title: 理解KMP
date: 2017-12-14 12:58:03
tags:


是什么?

Knuth-Morris-Pratt 算法(简称 KMP)是解决字符串匹配问题的常用算法之一,这个算法是由高德纳(Donald Ervin Knuth)和沃恩 · 普拉特在 1974 年构思,同年詹姆斯 ·H· 莫里斯也独立地设计出该算法,最终三人于 1977 年联合发表。

干什么的?

KMP算法一般用于解决字符串匹配问题

有主字符串P = “abcda”, 模式串 T=“cda”。问T是否出现在P中?若出现过指出出现的起始位置。

暴力匹配

介绍完KMP后回到字符串匹配这个问题,我第一次看到这个问题时,想到的最简单也就是最无脑的暴力匹配:
维护两个下标 i j.分别用来标识正在匹配字符在 P , T 两个字符串中的索引。�遍历字符进行匹配, T[j] 在与 P[i] 匹配的过程中若出现不匹配的字符,就让 i 退回 j 个(i = i - j + 1),而 j 则回退到开始即 j = 0�。

以下是Java代码:


public class KMP {
    public static void main(String[] args) {
        System.out.print(fKMP("abadda", "da"));
    }

    static int fKMP(String p, String t) {
        int i = 0;
        int j = 0;
        int pL = p.length();
        int tL = t.length();

        while (i < pL && j < tL) {
            if (p.charAt(i) == t.charAt(j)) {
                i += 1;
                j += 1;
            } else {
                i = i - j + 1;
                j = 0;
            }

        }

        if (tL == j) {
            return i - j;
        }
        return -1;
    }
}

我们来分析这个算法的时间复杂度O(mn),显然这样的复杂度在有些情况下确实有点效率不高:当T中有字符串重复出现那么,有些匹配就是徒劳的,分析他之所以不高的原因是:当某一位字符匹配�失败的时候 j 和 i 都会回退,而在�回退的时候有些回退是不必要的。�因为在以及匹配完成的字符里若出现过重复的,再进行一次匹配则是徒劳的。

改进

如何才能避免出现对已经比对过得字符串�重复的进行比对呢?

我们根据经验试着推出

1.png

上图中 p[3] 和 t[3] 比对失配后,按照暴力匹配的流程 i 会回滚p[1],j回滚到 t [0],但是回滚后p[1]和必然是不匹配的,因为在回滚前已经匹配过的 ABA 中 A 已经出现过一次,所以将模式串向右移动 2 个位置正好。

2.png

�移动后 i 还是不变,而 j 则是回滚到了 t[1]。

3.png

上面这个在匹配过程中,在匹配到 p[5] 与 t[5] 时�失配,我们发现 AB 在模式串中出现过两次,我们把模式串向前移动 3 个位置。接着匹配

4.png

移动完成后 i 的位置不动依然在 p[5] ,�而 j 则是回滚到了t[2]。

总结规律:模式串 t 中,若存在一些相同的字符,而我们模式串移动的位置(其实就是j的位置移动)则和这些重复出现的字符串长度有关.

next数组

在KMP�算法中有个很重要的�的概念那就是next数组;关于 next 数组的定义:该字符串在某一位字符前左前缀与右前缀两个集合中,重合�元素中长度最长元素的长度。

如:
字符串ABAAB,其最左前缀(不包括最后一个字符)有{A,AB,ABA,ABAA},最右前缀(不包括第一个字符)有{B,AB,AAB,BAAB}。

当i=0时,字符串为"A",,此时最左前缀为空,最右前缀也为空,next[0]==空;

当i=1时,字符串为"AB",,此时最左前缀为{A},最右前缀为{B},两个结合重合的元素为空 所以next[1]==0;

当i=2时,字符串为"ABA",,此时最左前缀为{A,AB},最右前缀为A,BA},重合的元素为 A 所以next[2]==1;

当i=3时,字符串为"ABAA",,此时最左前缀为{A,AB,ABA},最右前缀为{A,AA,BAA},两个集合重合的元素为 A 所以next[3]==1;

当i=4时,字符串为"ABAAB",,此时最左前缀为{A,AB,ABA,ABAA},最右前缀为{B,AB,AAB,BAAB},两个集合中重合的元素A、AB。 最长为AB, 所以next[4]==2;

对于字符串“ABCDABD”:

5.jpeg

这样的方法们就能轻而易举的算出next数组。

但是此时的next数组和真正的数组还有一些区别:

我们在求next数组的时候,对当前字符是不关心的,我们只关心这个字符之前最大公共元素的长长度。所以将刚才的next数组向右移动一位。空位补-1。
这样符合KMP的next数组就求出来了。

以下是求出next数组的Java代码

 private static int[] getNext(String t) {
        int next[] = new int[t.length()];
        int p_len = t.length();
        int i = 0;
        int j = -1;
        next[0] = -1;

        while (i < p_len - 1) {
            if (j == -1 || t.charAt(i) == t.charAt(j)) {
                i++;
                j++;
                next[i] = j;
            } else {
                j = next[j];
            }

        }
        return next;
    }

完整代码


public class KMP {
    public static void main(String[] args) {
        System.out.print(fKMP("“abcda”", "bcda"));
    }

    static int fKMP(String p, String t) {
        int i = 0;
        int j = 0;
        int pL = p.length();
        int tL = t.length();

        int[] next = getNext(t);

        while (i < pL && j < tL) {
            if (j == -1 || p.charAt(i) == t.charAt(j)) {
                i += 1;
                j += 1;
            } else {
                j = next[j];
            }

        }

        if (tL == j) {
            return i - j;
        }
        return -1;
    }

    private static int[] getNext(String t) {
        int next[] = new int[t.length()];
        int p_len = t.length();
        int i = 0;
        int j = -1;
        next[0] = -1;

        while (i < p_len - 1) {
            if (j == -1 || t.charAt(i) == t.charAt(j)) {
                i++;
                j++;
                next[i] = j;
            } else {
                j = next[j];
            }

        }
        return next;
    }
}

一句话

一句话来总结KMP:对于KMP算法来说只有当模式串中出现重复字符,KMP算法才能发挥它的作用。

参考

https://www.61mon.com/index.php/archives/183/

https://www.cnblogs.com/zhangtianq/p/5839909.html
s
http://www.cnblogs.com/yjiyjige/p/3263858.html

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