kmp算法

KMP算法介绍

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度为 O(m+n)

KMP 方法算法就利用之前判断过信息,通过一个 next 数组,保存模式串中前后最长公共子序列的长度,每次
回溯时,通过 next 数组找到,前面匹配过的位置,省去了大量的计算时间

举例

  1. 有一个字符串 S1 = "BBCABCDABABCDABCDABDE" , 搜索词 字符串 S2 = "ABCDABD"
  2. 现在要判断 S1 是否含有 S2, 如果存在,就返回第一次出现的位置, 如果没有,则返回-1
  3. 要求:使用KMP算法完成判断,不能使用简单的暴力匹配算法

解题思路

  1. 首先,用 S1 的第一个字符和 S2 的第一个字符去比较,不符合,关键词向后移动一位


    image.png
  2. 重复第一步,还是不符合,再后移


    image.png
  3. 一直重复,直到S1有一个字符与S2的第一个字符符合为止


    image.png
  4. 接着比较字符串和搜索词的下一个字符,还是符合。


    image.png

5.遇到 S1 有一个字符与 S2 对应的字符不符合。


image.png

6.如果按照暴力匹配算法的思路的话, 就会出现如下图的情况, 重复1-5步, 但是这种算法效率太低。
当第5步中空格与 D 不匹配时,你其实知道前面六个字符是”ABCDAB”了, 于是KMP 算法的思想是,设法利用这个已知信息,不要把”搜索位置”移回已经比较过的位置,继续把它向后移,这样就提高了效率。


image.png

7.怎么做到把刚刚重复的步骤省略掉?可以对 S2 计算出一张《前后缀最大公共元素表》

8.已知空格与 D 不匹配时,前面六个字符”ABCDAB”是匹配的。查表可知,最后一个匹配字符 B 对应, 对应的子串为ABCDAB,最大的公共元素长度为 2,因此按照下面的公式算出向后移动的位数:
移动位数 = 已匹配的字符数 - 最大的公共元素长度
因为 6 - 2 等于 4,所以将搜索词向后移动 4 位

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


image.png

10.因为空格与 A 不匹配,继续后移一位。


image.png

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


image.png

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


image.png

搜索词 前后缀最大公共元素表

image.png

Java代码实现

import java.util.Arrays;

public class KMP {

    public static void main(String[] args) {
        String s1 = "BBC ABCDAB ABCDABCDABDE";
        String s2 = "ABCDABD";
        
        int[] next = kmpNext(s2); 
        System.out.println("next=" + Arrays.toString(next));
        
        int index = kmpSearch(s1, s2, next);
        System.out.println("index=" + index); 
        
        
    }
    
    /**
     * 
     * @param s1 源字符串
     * @param s2 搜索子串
     * @param next 搜索子串对应的公共元素表
     * @return 如果是-1 就是没有匹配到,否则返回第一个匹配的位置
     */
    public static int kmpSearch(String s1, String s2, int[] next) {
        
        //遍历 
        for(int i = 0, j = 0; i < s1.length(); i++) {
            
            //需要处理 s1.charAt(i) != s2.charAt(j), 去调整j的大小
            while( j > 0 && s1.charAt(i) != s2.charAt(j)) {
                // 找到比对过Match的搜索字符串中的 对应的公共元素个数, 然后再从后一个字符开始比较
                j = next[j-1]; 
            }
            
            if(s1.charAt(i) == s2.charAt(j)) {
                j++;
            }
            // 所有的字符串都比对成功, 返回找到的字符串位置
            if(j == s2.length()) {
                return i - j + 1;
            }
        }
        return  -1;
    }

    /**
     * 前后缀最大公共元素表
     * @param dest
     * @return
     */
    public static int[] kmpNext(String dest) {
        //创建一个next 数组保存公共元素值
        int[] next = new int[dest.length()];
        //如果字符串是长度为1 公共元素值就是0
        next[0] = 0; 
        
        // i=1 搜索字符串从第二个字符开始计算公共元素
        for(int i = 1, j = 0; i < dest.length(); i++) {
            //当dest.charAt(i) != dest.charAt(j) ,我们需要从next[j-1]获取新的j
            while(j > 0 && dest.charAt(i) != dest.charAt(j)) {
                j = next[j-1];
            }
            //当dest.charAt(i) == dest.charAt(j) 满足时,公共元素就 +1
            if(dest.charAt(i) == dest.charAt(j)) {
                j++;
            }
            // 第i个元素时, 公共元素的个数
            next[i] = j;
        }
        return next;
    }
}

参考

https://www.cnblogs.com/zzuuoo666/p/9028287.html
https://www.bilibili.com/video/BV1E4411H73v?p=162

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