KMP算法详解

KMP是解决两个字符串匹配问题的非常好的算法,算法的时间复杂是O(n)。

现在假设场景是两个字符串str1,str2,求str2是否是str1的子串,如果是,返回第一个子串的第一个字母在str1中的索引。

例如:str1 = "ababcababtk";str2 = "ababtk";则返回5。

要使用KMP算法,首先要算出用来加速匹配的str2的数组next;

一、next数组的求解

next[n]中装的是str2中索引为n的字符前面的子串substr2前缀与后缀相等的最长前缀的长度,前缀与后缀的要求是,前缀一定要包含子串substr2的第一个字符,但是不能包含最后一个字符,后缀一定要包含子串substr2的最后一个字符,但是不能包含最后一个字符。

比如对于字符串str2中的't'前面的子串就是abab,它的前缀是"a","ab","aba";后缀是"b","ab","bab"。前缀与后缀相等的最长前缀的长度是2,所以't'对应的next数组的值是2,即next[4] = 2。

规定next[0] = -1,next[1] = 0,这是人为规定的,当然如果str2的长度为1,那么next数组就只有一个-1。

下面介绍怎么利用前面的next值求后面的next的值。

假设str中n-1位置求出的最长相等前后缀长度是m,要求n位置的最长相等前后缀长度即next[n],下面为了方便表述,将str字符串看成一个str字符数组。

1、str[n-1] = str[m],如下图,n-1和m的位置都是'k',所以'b'对应的next[n] = m+1;

str[n-1] = str[m]

2、str[n-1] != str[m],如下图,则看字符't'对应的next[m],假设next[m] = u,如果next[u]=next[m],则'b'对应的next[n] = u+1,否则再看next[u],按照上述步骤继续进行,直到next[x] = -1为止,此时next[n] = 0;

str[n-1] != str[m]

使用上述方法求出的str2的next数组是[-1,0,0,1,2,0]。


public static int[] getNext(String str){
        // 如果str长度为1,直接返回只含-1的数组
        if(str.length() == 1){
            return new int[]{-1};
        }
        int[] next = new int[str.length()];
        char[] str2 = str.toCharArray();
        // 首先将人为设定最长相等前缀的值填好
        next[0] = -1;
        next[1] = 0;
        int n = 2;
        // m代表i前面一个字符的最长相等前缀的长度,最开始i=2,next[i-1] = 0,所以n的初始值是0;
        int m = 0;
        while(n < next.length){
             // 这是情况一,str[n-1] = str[m],此时next[n] = m+1
            if(str2[m] == str2[n-1]){
                next[n++] = ++m;
             // 这是情况二,str[n-1] != str[m],此时,m = next[m],再次比较
            }else if(m > 0){
                m = next[m];
                // 当m=0时,则next[n] = 0;
            }else{
                next[n++] = 0;
            }
        }
        return next;
    }

二、使用next数组加速匹配

还是上述问题,str1 = "ababcababtk";str2 = "ababtk",上面已经求出str2的next数组是[-1,0,0,1,2,0],p1是指向str1当前匹配位置的指针,p2是指向str2当前位置的指针,开始匹配时会出现下面的情况。为了方便描述,还是将str1和str2看成两个字符数组。

1、str1[p1] == str2[p2] ,则p1和p2都往后移一位。

str1[p1] == str2[p2]

2、str1[p1] != str2[p2]且p2 != 0,则p2 = next[p2],p1不动。

str1[p1] != str2[p2]且p2 != 0

3、str1[p1] != str2[p2]且p2==0,p2不移动,p1向后移动一位。

str1[p1] != str2[p2]且p2==0

如果p2 = str2.length,停止上述过程,此时子串的第一个字符在str1中的位置是p1-p2,否则直到str1遍历完,之后若p2 != str2.length,则str1中不包含str2,否则子串的第一个字符在str1中的位置是p1-p2。

完整代码实现如下:

public static void main(String[] args) {
        String str1 = "ababcababtk";
        String str2 = "ababtk";
        int[] next = getNext(str2);
        int res = KMP(str1,str2,next);
        System.out.println(res);
    }

    public static int KMP(String str1,String str2,int[] next){
        if(str1 == null || str2 == null || str2.length() < 1 || str1.length() < str2.length()){
            return -1;
        }
        char[] strArr1 = str1.toCharArray();
        char[] strArr2 = str2.toCharArray();
        int p1 = 0;
        int p2 = 0;
        while(p1 < strArr1.length && p2 < strArr2.length){
            // 情况1,strArr1[p1] == strArr2[p2] ,则p1和p2都往后移一位
            if(strArr1[p1] == strArr2[p2]){
                p1++;
                p2++;
            // 情况3,str1[p1] != str2[p2]且p2==0,p2不移动,p1向后移动一位
            }else if(p2 == 0){
                p1++;
            // 情况2,str1[p1] != str2[p2]且p2!=0,则p2 = next[p2],p1不动。
            }else{
                p2 = next[p2];
            }
        }
        return p2 == strArr2.length ? p1-p2:-1;
    }

    public static int[] getNext(String str){
        // 如果str长度为1,直接返回只含-1的数组
        if(str.length() == 1){
            return new int[]{-1};
        }
        int[] next = new int[str.length()];
        char[] str2 = str.toCharArray();
        // 首先将人为设定最长相等前缀的值填好
        next[0] = -1;
        next[1] = 0;
        int n = 2;
        // m代表i前面一个字符的最长相等前缀的长度,最开始i=2,next[i-1] = 0,所以n的初始值是0;
        int m = 0;
        while(n < next.length){
             // 这是情况一,str[n-1] = str[m],此时next[n] = m+1
            if(str2[m] == str2[n-1]){
                next[n++] = ++m;
             // 这是情况二,str[n-1] != str[m],此时,m = next[m],再次比较
            }else if(m > 0){
                m = next[m];
                // 当m=0时,则next[n] = 0;
            }else{
                next[n++] = 0;
            }
        }
        return next;
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容