KMP算法 --- 在文本中寻找目标字符串

很多时候,为了在大文本中寻找到自己需要的内容,往往需要搜索关键字。这其中就牵涉到字符串匹配的算法,通过接受文本和关键词参数来返回关键词在文本出现的位置。一般人在初次接触的时候,可能会写出这样的代码:

/* 返回字符串substr在str中首次出现的位置索引,* 若不存在,返回-1。*/
int strStr(string str, string substr) {int i, j;if (str.empty() && substr.empty())return 0;for (i = 0; i < str.length(); ++i){for (j = 0; i + j < str.length() && j < substr.length(); ++j)if (str[i + j] != substr[j])break;if (j == substr.length())return i;}return -1;
}

这种算法的大致流程如下:

  若文本的长度为m,关键词的长度为n,则该算法的复杂度为O(mn)。这会引起某些情况下搜索效果会变得非常差,比如文本 000000...00001(在1的前面有10000个0),我们需要搜索的是关键词00...001(在1的前面有1000个0),找到关键词需要执行的步数将大致为 10000 * 1000 ,所以我们需要一个搜索效率更高的算法,即KMP算法。

  KMP算法全称为The Knuth-Morris-Pratt Algorithm,是由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现。它是通过利用匹配失败后的信息,尽可能减少文本与关键词的匹配次数从而达到快速匹配的目的。我们需要通过O(n)的函数来构造一个next数组,用于储存关键词的局部匹配信息,然后通过O(m)的遍历来寻找目标关键词。因此总的时间复杂度可以达到O(m+n)。

  如果使用KMP算法的话,流程可以简化为下图:

  
  可以看到(6)到(7)跳过了中间的两个字符,这也是因为我们知道这中间的两个字符不可能和关键词的第一个字符匹配,可以直接跳过。这样的话我们需要寻找下一个符合关键词前缀的位置,这个前缀可以是 i ,is等。当然只是这样的话对于上述的极端情况似乎没有效果,接下来开始深入探讨KMP算法。
  

1.在关键词中寻找其最长公共前后缀

  现在有一关键词: abcabba。
  我们可以从关键词的前1个字符构成的子串开始寻找最长公共前后缀,然后是前2个...一直到整个关键词的最长公共前后缀。

  这样就有:

  通过这些数据,我们就可以用来直接从前缀跳到后缀所在的位置,从而不需要逐个比对中间那些字符。

2.跳转数组的应用与构造

  上面的那个数组还不是next数组。在构造next数组之前,我们先需要了解其用途。next数组的含义是:在匹配到关键词索引值为 j 的字符(注意:这里 j 是从 0 开始的)失败的时候,文本跳过 j - next[j] 个字符的位置,然后从关键词索引为 next[j] 的字符继续匹配。

i += j - next[j];
j = next[j];

  

与关键词第一个字符不匹配的情况(匹配失效)


  在这里我们需要标记 next[0] 为 -1,用以表示上述特殊情况。这样文本将来到下一个字符的位置(j - next[j]的值刚好就是1),然后继续和关键词索引为 0 的字符继续比较。

与关键词中间某个字符不匹配的情况

情况1 (匹配的部分没有公共前后缀)


  在这种情况下,由于已经匹配的部分没有公共前后缀,此时next[4]的值为0,所以原来已经匹配的部分可以全部跳过,然后重新与关键字索引next[4]的值比较。如果不匹配,就会回到匹配失效的情况。

情况2(匹配的部分有公共前后缀)


  由于j = 4时,出现了 s 和 p 的不匹配,而已经匹配的部分包含公共前后缀 i ,这样在我们令索引 i 跳到下一个 字符 i 出现的位置。同时由于前缀 i 在原来的位置是已经匹配的, 那么跳转到后缀 i 位置的时候也肯定是匹配的。我们将 j 设为索引 next[4](这里 next[4] 的值为1)然后进行比较即可。

情况3(公共前后缀内部也含有公共前后缀)


  可以看到,已经匹配的部分含公共前后缀 aba ,而 aba 内部也含有一个公共前后缀 a 。因此我们需要先跳转到下一个公共前后缀 aba ,此时 next[j] 的值应为 3 ,所以从关键词索引 3 的字符继续比对。然而此时匹配依然失败,由于 aba 的公共前后缀是 a, 此时 next[j] 的值应为 1,因此跳转到下一个a,最终比对成功。
  这样的话,如果一个公共前后缀内部仍含有公共前后缀,我们需要通过上面的两行代码继续跳转(这是一种递归操作),直到匹配成功、 没有子公共前后缀 或者 匹配失效 的情况。

情况4(关键字内匹配和不匹配的部分构成的子串含有公共前后缀 且 前缀第一个字符与后缀最后一个字符 相等)


  可以看到关键字子串 abacaba 内含公共前后缀 aba ,而如果在这里将 next[6] 设置为 2 的话,则在比对的时候又是拿 a 来与 c 比较。同样关键字子串 aba 的公共前后缀是 a,如果将 next[2] 设为 0,则同样还是拿 a 和 c 做比较。结果一定会是匹配失效的,也就是最终会令j 变为 -1。这样的话我们可以直接令 next[2] 和 next[6] 直接设置为 -1,以减少不必要的跳转。

在构造next数组的时候还需要注意这种情况(关键词 aabaaac ,注意长度5和6的子串):

令 pos = next[pos] ,只要pos不为 -1,可以说明找到了长度更小的公共前后缀。
这是关键词 abacabad 的next数组:

以下是next数组的构造函数的实现。代码比较简洁,需要结合上述情况理解:

vector<int> construct_next(string key)
{//关键词为空时,next数组也为空if (key.empty())return vector<int>();int pos = 0, sz = key.length();vector<int> next(sz);   //next数组容量为sznext[0] = -1;for (int i = 1; i < sz - 1;){//匹配失效 或 找到公共前后缀时if (pos == -1 || key[i] == key[pos]){//情况4if (key[i] == key[0])next[i] = -1;next[++i] = ++pos;}//不匹配时,寻找匹配子串的公共前后缀elsepos = next[pos];}return next;
}

3.kmp函数的实现

  有了next数组后,kmp函数的实现就会简单的多了。这里是kmp函数的实现:

int kmp(string text, string key)
{vector<int> next = construct_next(key);int i = 0, j = 0, txtlen = text.length(), keylen = key.length();while (i <= txtlen - keylen && j < keylen){//匹配失效时,令j回归0;匹配成功时,给j加上1if (j == -1 || text[i + j] == key[j])++j;else{//进行跳转i += j - next[j];j = next[j];}}if (j == keylen)return i;elsereturn -1;
}

转载于:https://www.cnblogs.com/X-Jun/p/7070688.html

KMP算法 --- 在文本中寻找目标字符串相关推荐

  1. leetcode28 Implement strStr() 在字符串中寻找目标字符串

    题目要求: 在子字符串中寻找目标字符串,并返回该字符串第一次出现时的下标 在尝试的写了一提中等难度的题目后,又一次回到简单难度的题寻找温暖T-T 思路一 在原字符串中中寻找目标字符串首字母的下标,并提 ...

  2. 提取文本中的汉字字符串

    java 编程点滴 提取文本中的汉字字符串 提取文本中的汉字字符串 代码中含有中文字符,希望将代码中的中文字符提取出来,输出到数据库表格,然后补充对应的英文翻译. 继续处理代码,将文中的中文字符,通过 ...

  3. 比KMP算法更简单更快的字符串匹配算法

    我想说一句"我日,我讨厌KMP!". KMP虽然经典,但是理解起来极其复杂,好不容易理解好了,便起码来巨麻烦! 老子就是今天图书馆在写了几个小时才勉强写了一个有bug的.效率不高的 ...

  4. python 求子字符串_(6)KMP算法(求子串的位置)______字符串的匹配

    问题: 已知字符串 B 是字符串 A 的一个子串,问字符串 B 在字符串 A 的第一次出现位置. 暴力方法:从 A 字符串 的每个位置开始对字符串 B 进行匹配. 这种方法根据数据的不同 复杂度不同最 ...

  5. 算法:数组中寻找两个数字的和等于固定值

    数组中寻找两个数字的和等于固定值 下面为实现思路及代码 codes. // 思路 // 先进后出 两端逼近 // for example x + y = z 前置条件:z的值固定 // 故x固定 则y ...

  6. php 截取base64内容,PHP-从长文本中删除Base64字符串

    我真的很想知道我是否真的是第一个问这个问题的人,还是我如此盲目地找到一些关于这个问题的信息- 我有一个较长的文本,我想剥离它的base64编码的字符串 I am a text and have som ...

  7. python抓取文本字段_使用Python提取文本中含有特定字符串的方法示例

    今天搞了一天的文本处理,发现python真的太适合做数据处理了.废话不多说,一起学习吧! 1.我的原始数据是这样的,如图 2.如果要提取每行含有pass的字符串,代码如下: import re fil ...

  8. shell批量替换文本中的多种字符串

    需求,需要把文件中,aa替换成AA,bb替换成BB, cc替换成CC, 脚本如下: SRC_STR=(aa bb cc) DST_STR=(AA BB CC) CMAKE_CONFIG=${DST_S ...

  9. 为什么说在KMP算法中文本串中的每个字符都是需要进行比较操作的?

     KMP算法需要计算一个shift或者next表,这个表是一个部分匹配表,通过这个next表来计算当字符不匹配的时候移动的位数,这个移动位数的计算公式为 移动位数 = 已匹配的字符数 - 对应的n ...

  10. KMP算法下,长为n的字符串中匹配长度为m的子串的复杂度为O(m+n)

    kmp算法完成的任务是:给定两个字符串O和f,长度分别为n和 m,判断f是否在O中出现,如果出现则返回出现的位置.常规方法是遍历O的每一个位置,然后从该位置开始和f进行匹配,但是这种方法的复杂度是 O ...

最新文章

  1. iOS12 UITabbar Item 向上漂移错位的bug
  2. Spring Boot中实现跨域的五种方式
  3. Dataset之DA:数据增强(Data Augmentation)的简介、方法、案例应用之详细攻略
  4. vb.net datagridview数据批量导入sql_【自学C#】|| 笔记 44 ComboBox:组合框控件数据绑定...
  5. python中dic_python之dic {字典}(重要指数*****)
  6. IT大神提升代码效率的秘密,都私藏在这10个神仙软件里
  7. asp.net 在线 mp3,wma, avi
  8. [Linux]磁盘端口I/O
  9. [LeetCode] NO. 349 Intersection of Two Arrays
  10. 考研经验贴 and 一些感想
  11. 26.1-2 知识产权与标准规范(标准规范)
  12. html中pt与px的转换,ptpx换算(pt和像素换算)
  13. 【基于狂神Docker双响曲】:2、Docker进阶
  14. R分层抽样(Stratified Sampling)
  15. 北京WIFI密码,很强大
  16. 盘点大数据开发常用的四种编程语言
  17. pycharm在ubuntu中不能输入中文的问题
  18. 脑洞成现实!AI系统可提前10s预测地震
  19. 【Pygame】细致讲解开发Flappy Bird小游戏
  20. 央视报道69批次婴儿奶粉含三聚氰胺(含名单)

热门文章

  1. BZOJ 3329 Xorequ 数字DP+矩阵乘法
  2. 操作系统随机密码,定时改密码
  3. SSL ×××和IPSec的主要区别
  4. 2010年年度 “中国智能建筑品牌奖”获奖名单
  5. Nginx 凭啥并发数可以达到 3w?
  6. 腾讯,开源了,高性能 RPC 框架,是要干DUBBO 吗?
  7. Android 热补丁之 Tinker 原理解析
  8. 扇贝python课程免费_扇贝新推出的python课程值得买吗?
  9. 20172329《程序设计与数据结构》实验一:线性结构实验报告
  10. bootstrap datetimepicker 位置错误