KMP是一种著名的字符串模式匹配算法,它的名称来自三个发明人的名字。这个算法的一个特点就是,在匹配时,主串的指针不用回溯,整个匹配过程中,只需要对主串扫描一遍就可以了。因此适合对大字符串进行匹配。

搜了网上很多KMP的代码下来调试,发现不是下标越界,就是死循环的,相当诡异...最后重新拿起严老师那本《数据结构》来翻,各种费解,有个地方用下标值和字符串下标0的元素做判断,更是诡异了...

过了一天,忽然觉悟了。网上这些代码都是来自《数据结构》或者和他同源的版本的,而它使用的是以下标1为起始的字符串!对这种字符串组织格式,下标0存放的是字符串的长度

可是如今主流的语言,几乎都是用的下标0作为起始,书本上的代码显然没法用,那就自己重写一个吧。

算法的原理

字符串匹配嘛,无非就是两个指针,分别指向主串和模式串,然后依次往后移,检查是否一致。在遇到不能匹配的情况时(简称“失配”),一般的方法,就是让两个指针回溯,主串指针往后再移动一位,从头开始匹配。这其中做了很多重复劳动,我们可以分析一下:

可以看到模式串在匹配到下标5时失配了。
我们抓出模式串和主串在前方匹配的5个字符,并在模式串部分的前端主串部分的后端找到了一对最长的相等的字串(不等于原来的串),用阴影标记一下,后面有用。

接着移动模式串,继续匹配:

看出什么规律了么?每次比较,其实都是“abaab”的前端后端的字串进行比较:
第一回是"abaa"vs"baab"
第二回是"aba"vs"aab"
第三回是"ab"vs"ab"
可见,只有在模式串部分的前端主串部分的后端重合的时候,才可能继续匹配。

是这样么?当然是的,因为我们之前找出的是最长的,相等的字串!

这样就能把中间无效的对比步骤省略,主串的指针不变,模式串的指针直接跳到下标2继续匹配。这里的下标2就等于最长相等字串的长度。

接着推广到更一般的情形:
假设主串s,模式串patten,s和patten分别在下标i,j处失配,

如果j>0,那么,
显而易见,'si-k...si-1' = 'patten0...pattenk-1',此串长度为k,故下一步模式串指针应当跳转到下标k继续匹配。
在这里,因为'si-k...si-1' = 'pattenj-k...pattenj-1',得到'patten0...pattenk-1' = 'pattenj-k...pattenj-1',所以给定patten和j的情况下,k的值也是固定的。

如果j=0,那么i应当往后挪一位,j不变,重头匹配

至此,对于给定的patten,可以得到一个j->k的映射关系,记为数组next,其中,k = next[j]:
next[j] = Max{ k | 0<=k<j 并且 'patten0...pattenk-1' = 'pattenj-k...pattenj-1' }
当且仅当j == 0时,next[j] = -1(-1其实是没有意义的,在这里为了计算方便)

依照这个定义,已经可以写出一个计算next的弱弱的实现了。不过我先买个关子,先把主串的匹配搞定再说。

主串匹配算法

有了之前的分析,主串匹配的代码基本就可以一蹴而就了(Java代码):

static int Kmp(String s, String patten) {
int i = 0, j = -1;
int[] next = GetNext(patten);// 待实现 while (i < s.length() && j < patten.length()) {
if (j == -1 || s.charAt(i) == patten.charAt(j)) {
i++;
j++;
} else {
j = next[j];// 失配时跳转
}
} if (j == patten.length()) // 完全匹配
return i - j; return -1;
}

这儿有一处很巧妙地的地方:
next[0]是恒为-1的,所以如果在下标0处失配,则下一次循环j等于-1,i就会在循环中指向下一个字符,j也恢复为0。

模式串的next数组生成算法

看下面这张图

假设模式串上的下标i,模式串下的下标j,那么
显然next[5] = 2是由patteni=4 = pattenj=1推出的,
推广到一般的情况,也就是说当patten与自身错位匹配时,当他们在i,j(i>j)处匹配时,
此时可以得到next[i+1] = j+1
如果j = 0时就失配了的话,自然next[i+1]应当等于0

至此,写出代码也就不难了,有些小技巧却要注意一下(Java代码):

static int[] GetNext(String s) {
int i = 0, j = -1;
int[] next = new int[s.length()];
next[0] = -1; // 这个初始化时必须的 while( i<s.length()-1)
{
if( j == -1 || s.charAt(i) == s.charAt(j))
{
i++;
j++;
next[i] = j;
}
else
{
j = next[j];// 当j在下标零处失配,代码会怎么执行呢?
}
}
return next;
}

这个求next数组的方式和KMP算法的主体是不是很像呢?

每周一算法之六——KMP字符串匹配算法的更多相关文章

  1. BM和KMP字符串匹配算法学习

    BM和KMP字符串匹配算法学习 分类: 研究与学习 字符串匹配BM(Boyer-Moore)算法学习心得 http://www.cnblogs.com/a180285/archive/2011/12/ ...

  2. KMP字符串匹配算法理解(转)

    一.引言 主串(被扫描的串):S='s0s1...sn-1',i 为主串下标指针,指示每回合匹配过程中主串的当前被比较字符: 模式串(需要在主串中寻找的串):P='p0p1...pm-1',j 为模式 ...

  3. 【KMP】【字符串】KMP字符串匹配算法 学习笔记

    一.简介     KMP是由Knuth.Morris和Prat发明的字符串匹配算法,它的时间复杂度是均摊\(O(n+m)\).其实用Hash也可以做到线性,只不过Hash存在极其微小的难以避免的冲突. ...

  4. KMP字符串匹配算法翔解❤

    看了Angel_Kitty学姐的博客,我豁然开朗,写下此文: 那么首先我们知道,kmp算法是一种字符串匹配算法,那么我们来看一个例子. 比方说,现在我有两段像这样子的字符串: 分别是T和P,很明显,P ...

  5. 保证你能看懂的KMP字符串匹配算法

    文章转载自一位大牛: 阮一峰原网址http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm ...

  6. 子字符串substring 问题 - KMP 字符串匹配算法备忘录

    本文为自己对KMP的理解. 对KMP很好的介绍可以参考 http://www.cnblogs.com/yjiyjige/p/3263858.html 本文为对这篇文章的提炼和补充. KMP算法基本思想 ...

  7. KMP字符串匹配算法详解

    KMP算法利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的.具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息.时间复杂度O(m+n). Next()函数 ...

  8. [KMP]字符串匹配算法

    算法介绍: KMP是一种用来处理字符串匹配问题的算法,给你两个字符串A.B,让你回答B是否为A的子串,或者A中有多少子串等于B. 这题最暴力的做法是:枚举A中与B相等的子串的左端点,再判断是否与B相等 ...

  9. 图解KMP字符串匹配算法+代码实现

    kmp算法跟之前讲的bm算法思想有一定的相似性.之前提到过,bm算法中有个好后缀的概念,而在kmp中有个好前缀的概念,什么是好前缀,我们先来看下面这个例子. 观察上面这个例子,已经匹配的abcde称为 ...

随机推荐

  1. 安装Vmware Tools出现错误

    安装Vmware Tools出现: Before you can compile modules, you need to have the following installed... makegc ...

  2. c++基础_特殊回文数

    #include <iostream> using namespace std; int main(){ int n; cin>>n; ;i<;i++){ int tem ...

  3. Spider-Python爬虫之XPath 教程

    原文链接:https://www.runoob.com/xpath/xpath-syntax.html XPath 术语 XPath 节点 七种类型:在 XPath 中,有七种类型的节点:元素.属性. ...

  4. Python条件判断(if)

    Python条件判断(if) 一.基本介绍 1.Python 编程中 if 语句用于控制程序的执行,基本形式为: if 判断条件: 执行语句…… 需要注意的是,Python没有像其他大多数语言一样使用 ...

  5. jsp获取绝对路径----${pageContext.request.contextPath}

    JSP取得绝对路径 在JavaWeb开发中,常使用绝对路径的方式来引入JavaScript和CSS文件,这样可以避免因为目录变动导致引入文件找不到的情况,常用的做法如下: 一.使用${pageCont ...

  6. Microsoft Excel 准确按照一页的宽度和高度打印

    设置 Microsoft Excel 准确按照一页的宽度和高度打印 Sheet1. VBA复制  With Worksheets("Sheet1").PageSetup  .Zoo ...

  7. 状态压缩DP总结

    POJ1185 炮兵部队问题: 在平原上才能放置炮兵,每个炮兵的上下左右2格之内都不能出现别的炮兵 可以考虑在当前行放置炮兵它的右侧和下侧绝对不会出现炮兵即可,左侧和上侧就能省去考虑 明显的状态压缩d ...

  8. 解决Codeforces访问慢的本地方案

    参考: http://m.blog.csdn.net/blog/Xiangamp/42245923#

  9. hdu 4871 树的分治+最短路记录路径

    /* 题意:给你一些节点和一些边,求最短路径树上是k个节点的最长的路径数. 解:1.求出最短路径树--spfa加记录 2.树上进行操作--树的分治,分别处理子树进行补集等运算 */ #include& ...

  10. codeforces 362B

    #include<stdio.h> #include<stdlib.h> int cmp(const void *a,const void *b) { return *(int ...