字符串匹配的 KMP算法
一般字符串匹配过程
KMP算法是字符串匹配算法的一种改进版,一般的字符串匹配算法是:从主串(目标字符串)和模式串(待匹配字符串)的第一个字符开始比较,如果相等则继续匹配下一个字符, 如果不相等则从主串的下一个字符开始匹配,直到模式串被匹配完,则匹配成功,或主串被匹配完且模式串未匹配完,则匹配失败。匹配过程入下图:

这种实现方式是最简单的, 但也是低效的,因为第三次匹配结束后的第四次和第五次是没有必要的。
分析
第三次匹配在j = 0(a)和i = 2(a)处开始,在j = 4(c)和i = 6(b)处失败,这意味着模式串和主串中:j = 0(a)和i = 2(a)、j = 1(b)和i = 3(b)、j = 2(c)和i = 4(c)、j = 3(a)和i = 5(a)这四个字符相互匹配。
分析模式串的前3个字符:模式串的第一个字符j = 0是a,j = 1(b)、j = 2(c)这两个字符和j = 0(a)不同,因此以这两个字符开头的匹配必定失败,在第三次匹配中,主串中i = 3(b)、i = 4(c)和模式串j = 1(b)、j = 2(c)相互匹配,因此匹配失败后,可以直接跳过主串中i = 3(b)、i = 4(c)这两个字符的匹配。
继续分析模式串的j = 3(a)和j = 4(c)这两个字符,如果模式串匹配到j = 4(c)这个字符才失败的话,因为j = 4(c)的前一个字符j = 3(a)和第一个字符j = 0(a)是相同的,结合上一个分析得知:
1):下一次匹配中主串已经跳过了和
j = 3(a)前两个相互匹配的字符i = 3(b)、i = 4(c),将从i = 5(a)开始匹配。
2):j = 3(a)和i = 5(a)相互匹配。
因此下一次匹配认为j = 3(a)和i = 5(a)已经匹配过了,匹配从j = 4(b)和i = 6(b)开始,这样的话也跳过了j = 3(a)这个字符的匹配。
同理可得第二次匹配也是没必要的。
KMP算法
KMP算法匹配过程
利用KMP算法匹配的过程如下图:

KMP算法的改进之处在于:能够知道在匹配失败后,有多少字符是不需要进行匹配可以直接跳过的,匹配失败后,下一次匹配从什么地方开始能够有效的减少不必要的匹配过程。
next[n]求解方法
由上面的分析可以发现,KMP算法的核心在于对模式串本身的分析,其分析结果能提供在j = n位置匹配失败时,从j = 0到j = n - 1这个子串中前缀和后缀的最长公共匹配的字符数,这样说可能比较难以理解,看下图:

在得到子串前缀和后缀的最长公共匹配字符数l后,以后在i = x,j = n处匹配失败时,可以直接从i = x,j = l处继续匹配(证明过程参考:严蔚敏的《数据结构》4.3章),这样问题就很明显了,我们要求出n和l对应的值,其中n是模式串字符数组的下标,l的有序集合通常称之为next数组,前面两个模式串的next数组和下标n的对应如下:

模式串2完整匹配过程
有了这个next数组,那么在匹配的过程中我们就能在j = n处匹配失败后,根据next[n]的值进行偏移,其中next[0]固定为-1,代表在当前i这个位置整个模式串和主串都无法匹配成功,要从下一个位置i = i + 1及j = 0处开始匹配,模式串2的匹配过程如下:

现在知道了next数组的作用,也知道在有next数组时的匹配过程,那么剩下的问题就是如何通过代码求出next数组及匹配过程了。
求
next数组的过程可以认为是将模式串拆分成n个子串,分别对每个子串求前缀和后缀的最长公共匹配字符数l,这一点可以通过上图(最长公共匹配字符数)看出来(没有画出l=0时的图解)看出来。
代码实现
求next数组的代码如下:
void get_next(string pattern, int next[]) {
// !!!!!!!!!!由网友(评论第一条)指出该算法存在问题,已将有问题的代码注释并附上临时想到的算法代码。
// int i = 0; // i用来记录当前计算的next数组元素的下标, 同时也作为模式串本身被匹配到的位置的下标
// int j = 0; // j == -1 代表从在i的位置模式串无法匹配成功,从下一个位置开始匹配
// next[0] = -1; // next[0]固定为-1
// int p_len = pattern.length();
// while (++i < p_len) {
// if (pattern[i] == pattern[j]) {
// // j是用来记录当前模式串匹配到的位置的下标, 这就意味着当j = l时,
// // 则在pattern[j]这个字符前面已经有l - 1个成功匹配,
// // 即子串前缀和后缀的最长公共匹配字符数有l - 1个。
// next[i] = j++;
// } else {
// next[i] = j;
// j = 0;
// if (pattern[i] == pattern[j]) {
// j++;
// }
// }
// }
int j = ;
next[] = -;
int p_len = pattern.length();
int matched = ;
while (++j <= p_len) {
int right = j - ;
int mid = floor(right / );
int left = right % == ? mid - : mid;
int curLeft = left;
int curRight = right;
while (curLeft >= ) {
if (pattern[curLeft] == pattern[curRight]) {
matched++;
curLeft--;
curRight--;
} else {
matched = ;
curLeft = --left;
curRight = right;
}
}
next[j] = matched;
matched = ;
}
}
根据next数组求模式串在主串中的位置代码如下:
int search(string source, string pattern, int next[]) {
int i = ;
int j = ;
int p_len = pattern.length();
int s_len = source.length();
while (j < p_len && i < s_len) {
if (j == - || source[i] == pattern[j]) {
i++;
j++;
}
else {
j = next[j];
}
}
if (j < pattern.length())
return -;
else
return i - pattern.length();
}
测试代码如下:
int main() {
string source = "ABCDABCEAAAABASABCDABCADABCDABCEAABCDABCEAAABASABCDABCAABLAKABCDABABCDABCEAAADSFDABCADABCDABCEAAABCDABCEAAABASABCDABCADABCDABCEAAABLAKABLAKK";
// string pattern = "abcaaabcab";
string pattern = "ABCDABCEAAABASABCDABCADABCDABCEAAABLAK";
int next[pattern.length()] = { NULL };
get_next(pattern, next);
cout << "next数组: \t";
for (int i = ; i < pattern.length(); i++)
cout << next[i] << " ";
cout << endl;
int pos = search(source, pattern, next);
if (- != pos) {
cout << "匹配成功,模式串在主串中首次出现的位置是: 第" << pos + << "位";
getchar();
return ;
} else {
cout << "匹配失败";
}
getchar();
return ;
}
执行结果:
next数组: -
匹配成功,模式串在主串中首次出现的位置是: 第97位
KMP算法优化
再回过头去看模式串2的next数组的图:

如果模式串和主串的匹配在j = 6(b)处失败的话,根据j = next[6] = 1得知下一次匹配从j = 1处开始,j = 1处的字符和j = 6处的字符同为c,因此这次匹配必定会失败。
同样的,模式串和主串的匹配在j = 7(c)处或在j = 9(b)处失败的话,根据next数组偏移后下一次匹配也必定会失败。
考虑如果模式串是: aaaac,根据一般的KMP算法求出的next数组及匹配过程如下:

显而易见,在第二次匹配失败后,第三、四、五次匹配都是没有意义的,j = next[3]、j = next[2]、j = next[1]、j = next[0]这四处的字符都是a,在j = 3(a)处匹配失败时,根据模式串本身就应该可以得出结论:可以跳过j = 2(a)、j = 1(a)、j = 0(a)的匹配,直接从i = i + 1 、j = 0处开始匹配,所以优化过后的next数组应该是:

代码实现
优化后的求next数组的代码如下:
void get_next(string pattern, int next[]) {
// !!!!!!!!!!由网友(评论第一条)指出该算法存在问题,更新后的代码在上方,新算法的优化代码暂未实现,但是优化思路是正确的。
// int i = 0; // i用来记录当前计算的next数组元素的下标, 同时也作为模式串本身被匹配到的位置的下标
// int j = 0; // j == -1 代表从在i的位置模式串无法匹配成功,从下一个位置开始匹配
// next[0] = -1; // next[0]固定为-1
// int p_len = pattern.length();
// while (++i < p_len) {
// if (pattern[i] == pattern[j]) {
// // j是用来记录当前模式串匹配到的位置的下标, 这就意味着当j = l时,
// // 则在pattern[j]这个字符前面已经有l - 1个成功匹配,
// // 即子串前缀和后缀的最长公共匹配字符数有l - 1个。
// next[i] = j++;
//
// // 当根据next[i]偏移后的字符与偏移前的字符向同时
// // 那么这次的偏移是没有意义的,因为匹配必定会失败
// // 所以可以一直往前偏移,直到
// // 1): 偏移前的字符和偏移后的字符不相同。
// // 2): next[i] == -1
// while (next[i] != -1 && pattern[i] == pattern[next[i]]) {
// next[i] = next[next[i]];
// }
// } else {
// next[i] = j;
// j = 0;
// if (pattern[i] == pattern[j]) {
// j++;
// }
// }
// }
}
字符串匹配的 KMP算法的更多相关文章
- Luogu 3375 【模板】KMP字符串匹配(KMP算法)
Luogu 3375 [模板]KMP字符串匹配(KMP算法) Description 如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置. 为了减少骗分的情况,接下来 ...
- 字符串匹配的kmp算法 及 python实现
一:背景 给定一个主串(以 S 代替)和模式串(以 P 代替),要求找出 P 在 S 中出现的位置,此即串的模式匹配问题. Knuth-Morris-Pratt 算法(简称 KMP)是解决这一问题的常 ...
- HDU 1711 Number Sequence (字符串匹配,KMP算法)
HDU 1711 Number Sequence (字符串匹配,KMP算法) Description Given two sequences of numbers : a1, a2, ...... , ...
- 字符串匹配(KMP 算法 含代码)
主要是针对字符串的匹配算法进行解说 有关字符串的基本知识 传统的串匹配法 模式匹配的一种改进算法KMP算法 网上一比較易懂的解说 小样例 1计算next 2计算nextval 代码 有关字符串的基本知 ...
- 实现字符串匹配的KMP算法
KMP算法是Knuth-Morris-Pratt算法的简称,它主要用于解决在一个长字符串S中匹配一个较短字符串s. 首先我们从整体来把我这个算法的思想. 字符串匹配的朴素算法: 我们容易想到朴素算法, ...
- 字符串匹配的KMP算法
~~~摘录 来源:阮一峰~~~ 字符串匹配是计算机的基本任务之一. 举例来说,有一个字符串”BBC ABCDAB ABCDABCDABDE”,我想知道,里面是否包含另一个字符串”ABCDABD”? 许 ...
- 字符串匹配的KMP算法详解及C#实现
字符串匹配是计算机的基本任务之一. 举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一个字符串"ABCDABD" ...
- 字符串匹配与KMP算法实现
>>字符串匹配问题 字符串匹配问题即在匹配串中寻找模式串是否出现, 首先想到的是使用暴力破解,也就是Brute Force(BF或蛮力搜索) 算法,将匹配串和模式串左对齐,然后从左向右一个 ...
- 字符串匹配的KMP算法(转)
转载:http://kb.cnblogs.com/page/176818/ 字符串匹配是计算机的基本任务之一. 举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE&quo ...
随机推荐
- Centos7——NFS(Network File System)服务
NFS(Network File System)即网络文件系统,允许计算机之间通过网络共享资源:在NFS客户端即可NFS服务端所共享的目录挂载到本地,此时即可像读写本地目录一样读写远程计算机的目录与文 ...
- Linux命令第四篇
作业四: 1) 新建目录/test/dir,属主为tom,数组为group1,/test目录的权限为777 # useradd tom [root@localhost /]# groupadd gr ...
- vim查找格式
使用了VIM这么久,却一直无法牢记一些基本的操作指令.今天查找一个关键字时,想不起来怎么查找“下一个”,于是google之并解决,顺便把有用的都贴过来罢. 查找指令:/xxx 往下查找?xxx 往上 ...
- Vue 开源项目库汇总(转)
最近做了一个Vue开源项目库汇总,里面集合了OpenDigg 上的优质的Vue开源项目库,方便移动开发人员便捷的找到自己需要的项目工具等,感兴趣的可以到GitHub上给个star.https://gi ...
- Linux:“awk”命令的妙用
awk是一个强大的文本分析工具,简单来说awk就是把文件逐行读入,(空格,制表符)为默认分隔符将每行切片,切开的部分再进行各种分析处理. 0.基本用法 awk是一个强大的文本分析工具,简单来说awk就 ...
- Print all attributes and values in a Javascript Object
function printObject(o) { var out = ''; for (var p in o) { out += '\n' + ':: ' + p + '(' + typeof(o[ ...
- MySQL之You can't specify target table for update in FROM clause解决办法
这篇文章主要介绍了mysql中You can't specify target table for update in FROM clause错误解决方法,需要的朋友可以参考下 MySQL中You c ...
- 连接postgres特别消耗cpu资源而引发的PostgreSQL性能优化考虑
由于是开发阶段,所以并没有配置postgres的参数,都是使用安装时的默认配置,以前运行也不见得有什么不正常,可是前几天我的cpu资源占用突然升高.查看进程,发现有一个postgres的进程占用CPU ...
- 【20171123】【GITC精华演讲】贝业新兄弟李济宏:如何做到企业信息化建设的加减乘除
导读 11月23日智慧物流论坛上,贝业新兄弟李济宏分享了<如何做到企业信息化建设的加减乘除>演讲,介绍了如何更好的构建企业信息化系统. 30秒get演讲干货 为什么用户总说系统难用?为什么 ...
- 从零开始unity特效(持续追加中)
打算重拾3d渲染了,计划把主要理论过一遍,每部分琢磨一个言之有物的demo. 因为很多东西要现学,再加上上班-8h,更新会比较慢,但会坚持. (待续) -------houdini+unity河流(2 ...