KMP算法 --- 在文本中寻找目标字符串
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数组容量为sz
next[0] = -1;
for (int i = 1; i < sz - 1;)
{
//匹配失效 或 找到公共前后缀时
if (pos == -1 || key[i] == key[pos])
{
//情况4
if (key[i] == key[0])
next[i] = -1;
next[++i] = ++pos;
}
//不匹配时,寻找匹配子串的公共前后缀
else
pos = 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加上1
if (j == -1 || text[i + j] == key[j])
++j;
else
{
//进行跳转
i += j - next[j];
j = next[j];
}
}
if (j == keylen)
return i;
else
return -1;
}
KMP算法 --- 在文本中寻找目标字符串的更多相关文章
- leetcode——Search for a Range 排序数组中寻找目标下标范围(AC)
Given a sorted array of integers, find the starting and ending position of a given target value. You ...
- Java利用PushbackReader实现返回对文本中的指定字符串之前的内容
import java.io.FileReader; import java.io.PushbackReader; public class PushbackTest { public static ...
- KMP算法,你想知道的都在这里!
简洁 我相信很多人都听说过KMP算法(PS:在上数据结构的时候,这个算法自始至终都没想明白) 大家也知道KMP算法是用来寻找目标子串的算法,但是都没有真正搞懂KMP.之前,我也是如此,我疑惑的有: N ...
- 回朔法/KMP算法-查找字符串
回朔法:在字符串查找的时候最容易想到的是暴力查找,也就是回朔法.其思路是将要寻找的串的每个字符取出,然后按顺序在源串中查找,如果找到则返回true,否则源串索引向后移动一位,再重复查找,直到找到返回t ...
- 字符串查找KMP算法(转)
如果你用过ctrl+F这个快捷键,那么你有很大的概率使用过这个算法,这就是在待查找字符串(可能有成千上万个字符)中找出模式串(比较小,可能有几个字符),可能找到大于或者等于1次的位置.例如,在abab ...
- 字符串查找KMP算法
如果你用过ctrl+F这个快捷键,那么你有很大的概率使用过这个算法,这就是在待查找字符串(可能有成千上万个字符)中找出模式串(比较小,可能有几个字符),可能找到大于或者等于1次的位置.例如,在abab ...
- 字符串匹配算法-kmp算法
一原理: 部分转自:http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html 字 ...
- 字符串模式匹配之KMP算法的next数组详解与C++实现
相信来看next数组如何求解的童鞋已经对KMP算法是怎么回事有了一定的了解,这里就不再赘述,附上一个链接吧:https://www.cnblogs.com/c-cloud/p/3224788.html ...
- KMP算法原理
前几天在看数据结构与算法,里面提到过kmp算法,一个超级经典的字符串匹配算法.虽然网上有一大堆关于kmp算法的介绍文章,但是我看过之后还是“不明觉厉”.所以打算自己写写,大家一起学习吧. 一.关于KM ...
随机推荐
- ssh代理上网
背景: 公司开发机没有外网,但可以通过ssh连接到另一台可以上公网的机器,所以想通过ssh代理的方式上网,简单又方便,而且需要的时候上,不需要的时候也可以不上 配置: 超级简单 在开发机上建立ssh隧 ...
- Bootstrap 常用组件汇总
Bootstrap 官方文档:http://www.bootcss.com/ Bootstrap Multiselect 多选下拉组件 官方文档:http://www.kuitao8.com/demo ...
- 对于JSONObject,我只是临时抱佛脚
说起JSON,大家就谈不上陌生了,因为对于数据传输语言,各位只认json,即使有XML语言,但是各位很少用吧.我也是,但是之前用过的json转换工具各种各样,我记忆中有过GSON(google).fa ...
- 关于Python中yield的一些个人见解
# 样例代码def yield_test(n): for i in range(n): yield call(i) print("i=",i) #做一些其它的事情 print(&q ...
- redisLock redis分布式锁
redis-lock redis setnx cmmand java object condition queue 条件队列 retrycount 带有重试次数限制 object wait time ...
- 可能是最详细的 Hexo + GitHub Pages 搭建博客的教程
前言:博主目前大三,Web 前端爱好者.写博客的好处,不是为了写而写,而是一个记录思想的过程.不要考虑它能带给你什么,而是你自己从中收获了什么. 最近刚好有空,于是就参照网上的各种教程,搭建了一个博客 ...
- 使用 XML 配置 MyBatis
构建 SqlSessionFactory 最常见的方式是基于 XML 配置(的构造方式).下面的 mybatis-config.xml 展示了一个 典型的 MyBatis 配置文件的样子: XML C ...
- csv导入数据到mysql
csv表中含有中文字符,具体实现代码示例: load data infile 'C:\\Users\\Administrator\\Desktop\\import\\CELLutf.csv' into ...
- php二进制流文件
<?php $img_file = 'test.png'; // $fp = fopen($img_file, 'rb'); // $content = fread($fp, filesize( ...
- Hibernate基础知识总结
Hibernate是JDBC的轻量级的对象封装(encapsulation),它是一个独立的对象持久persistence层框架. hibernate要做的事,就是让对象投影到关系数据库中,然后实施化 ...