KMP算法 - 基于《算法》第四版

基本思想

  1. 规定:匹配字符串 - 模式串(pat), 匹配文本 - 文本(txt)
  2. 基本思想:当出现不匹配时,就能知晓一部分文本的内容(因为在匹配失败之前它们已经和模式相匹配),根据这些已经知晓的内容决定 在出现不匹配时,模式应在处于哪个位置和文本的下一个字符比较 - 即找到已知晓内容和模式的最长公共前缀(利用模式去匹配已知晓的内容)

要点

看《算法》的时候,有点部分真的太简洁了,看的晦涩难懂( 是我太菜了~ /(ㄒoㄒ)/~~) ,关注一下几个要点,要结合书中内容看可能会有新的理解

  1. 指向文本的指针 i 永远不会回退,不会重复扫描文本,只有模式的指针 j 会进行回退。
  2. 有限状态机(DFA)只和 模式 有关,所以只要计算出了一个 模式的有限状态机(DFA),就可以匹配不同的文本。
  3. DFA[txt.charAt(i)][j] = next, 表示 当前状态 j 时,遇到 文本字符 txt.charAt(i) (即 txt.charAt(i) 和 pat.charAt(j)进行比较)后的下一个状态是 next ( 即模式的指针 j 需要回退/前进到 next 处 和 文本字符 txt.charAt(i + 1)进行比较) 。
  4. 在一个状态中,要确定状态会进行何种转移,需要知道 当前状态遇到的字符
  5. 构造DFA:匹配成功时,即txt.charAt(i) == pat.charAt(j),DFA[txt.charAt(i)][j] = j +1; 匹配失败时,模式指针 j 的回退也不是盲目回退, 它会根据 部分已经匹配成功的字符串与模式进行匹配后所处的状态(即书上所说的 X 重启状态) 以及 当前匹配失败的输入 来决定回退到哪个位置
    匹配图.png
    在箭头处出现了不匹配,那此时pat 指针 j 应该回退到哪个地方在和 txt 的下一个字符 A 比较呢?
    知道 状态 j 遇到了 字符D 发生了不匹配,意味着pat的前 j 个字符串 (0.. j-1)和文本的 (i - j, i - 1)是相匹配的, 就像上述所示,但是我们不用理会 txt.charAt(i-j),因为 i - j处已经不可能出现匹配,所以 部分已经匹配成功的字符串 就为 B A B A (pat[1… j - 1])。
    现我们考虑 B A B A 和 模式 进行匹配会到达什么状态(所到达的状态也是书中所提到的 重启状态 X),这个过程我们也可以看成是找 部分已经匹配成功的字符串模式最长公共前缀 的过程。
    匹配图2.png
    可以看到 B A B A 和 模式 进行匹配之后到达了 状态 3,即 X =3。则可以知道 DFA[D][5] = DFA[D][3],即在状态 5 遇到 字符 D发生不匹配时应该回退的位置 就是在状态 3 遇到 字符 D 时 应该到达的位置。 这可以对应到书中代码 DFA[C][j] = DFA[C][X]。
  6. 困扰我很久的一个 X 如何进行求得,可以将其看成一个 X[] 数组,记录了模式与 部分匹配成功的字符串(pat[1…j -1])所达到的所有状态(书本P765,图5.3.8很好的表达了此点)。它们之间关系是一个递推关系,X[i+1]为X[j]状态 遇到 pat.charAt(j)时所到达的状态,即 X[j + 1] = DFA[pat.charAt(j)][x[j]],X[0]初始化状态为0。 这也便是书中代码中的 X = DFA[pat.charAt(j)][X]。理解了上述6,7之后,就可以写出构造DFA的过程(当然,我对于上述6,7的说明都是基于你已经看过了《算法》中字符串查找部分哦~)
int[][] DFA;
public void generateDFA(String pat){
        int M = pat.length();
        int R = 256;  // ASCII字符不会操过256种
        DFA = new int[R][M];
        // 初始化状态0, 在状态0只有遇到了pat.charAt(0)才会向前推进, 遇到其他为0(java默认初始化数组为0)
        DFA[pat.charAt(0)][0] = 1;
        int X = 0;  // 初始化重启状态为0
        for (int j = 1; j < M; j++){  // 构造DFA数组过程
           for (int c = 0; c < R; c++)
          // 状态j遇到字符c不匹配时,把重启状态X遇到字符c到达哪个状态赋值DFA[c][j]
                DFA[c][j] = DFA[c][X];
           DFA[pat.charAt(j)][j] = j + 1;  // 匹配成功, 状态向前推进
           X = DFA[pat.charAt(j)][X];  // 部分已经成功匹配字符串中增加了pat.charAt(i), 需要更新重启状态X,即它们的最长重叠字符会发生变化
        }
}
  1. 我们已经计算出了DFA,下面是利用DFA来搜索文本的算法 - 结合书本P498 图5.3.7理解:
public int search(String txt) {
   int M = pat.length();
   int N = txt.length();
   int i,j;
    // pat 的初始态为 0 - 模拟有限状态机运行
   for (i = 0,j = 0; i < N && j < M; i++) {
        // 当前是状态 j,遇到字符 txt[i],
        // pat 应该转移到哪个状态?
        j = dp[txt.charAt(i)][j];
        // 如果达到终止态,返回匹配开头的索引
        if (j == M) return i - M;
   }
   // 没到达终止态,匹配失败
   return N;
}
  1. 结合上面两部分就可以得到具体的KMP算法啦! ( 具体参考书籍上的算法哦

总结

  1. 要使用KMP算法进行匹配,重要的是求出 DFA 数组,而要求出正确的得到DFA数组,格外需要关注(难理解)的是重启状态X和重启状态X的转移,即为每次发生匹配时,模式 和 pat[1..j]的最长公共前缀。只要得到了DFA数组之后,模拟有限状态机运行就可以进行匹配操作了。
  2. 对于长度为M的模式字符串和长度为N的文本,KMP查找算法访问字符串不会超过 N + M个。
  3. 就算书上所提的一样,KMP算法为最坏情况提供的线性级别运行时间保证的一个理论成果,在实际运用中,它比暴力算法的速度优势并不明显

参考资料

[1] https://judes.me/tech/2016/04/10/kmp.html
[2] https://book.douban.com/subject/19952400/discussion/59623403/
[3] https://zhuanlan.zhihu.com/p/83334559

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

推荐阅读更多精彩内容

  • 暴力子字符串查找算法隐式回退性能显式回退 Knuth-Morris-Pratt算法确定有限状态自动机DFA的构造性...
    zhixin9001阅读 337评论 0 0
  • KMP 算法(Knuth-Morris-Pratt 算法)是一个著名的字符串匹配算法,效率很高,但是确实有点复杂。...
    labuladong2阅读 646评论 0 3
  • 整理了一下据说由于过于晦涩难懂而导致某系统程序猿直接在实现字符串匹配的时候直接用暴力算法代替的KMP算法,初看之时...
    不可思议的Mark阅读 8,607评论 9 10
  • 子字符串的一种基本操作就是子字符串查找:给定一段长度为N的文本和一个长度为M的模式字符串,在文本中找到一个和该模式...
    sleepyjoker阅读 1,502评论 0 0
  • KMP的DFA理解对新手来说还是很比较费劲自动机原理如下图 我们先说其怎么样利用DFA,然后再实现DFA 其中最重...
    小烈yhl阅读 1,685评论 0 1