字符串模式匹配之KMP算法的next数组详解与C++实现
相信来看next数组如何求解的童鞋已经对KMP算法是怎么回事有了一定的了解,这里就不再赘述,附上一个链接吧:https://www.cnblogs.com/c-cloud/p/3224788.html,里面对KMP算法有详细的讲解,如果你还不了解KMP算法,可以看看~~。
下面就来讲解不容易理解但又很重要的next数组,相信这是你看过的最容易理解的next数组的讲解了(*^_^*)。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
一、首先,next数组是什么?
简单来说,假设我们有一个主串S和一个模式串T,并且想知道S是否包含T,如果包含,那么T第一次出现在S中的首字符在什么位置?有一种暴力求法:当S[i]!=T[j]的时候,j回溯到j=0,而i回溯到i=i-j+1,这种方法简答粗暴,但效率低下,时间复杂度的范围是(最好与最坏情况):O(n+m)~O(n*m),其中,n为主串S的长度,m为模式串T的长度;KMP算法对这种BF算法做了很大的改进,其基本思想是主串S不进行回溯,而是希望某趟在S[i]和T[j]匹配失败后,下标i不回溯,下标j回溯到某个位置K,使得T[K]对准S[i]继续进行比较。显然,关键问题是如何确定位置K。而这里的next数组表示的就是这个K值!需要注意的是,这个K值仅依赖于模式串T本身字符序列的构成,与主串S无关。
二、朴素模式匹配算法BF
这里给出BF代码,很简单,就不做具体说明了。
int BF(string S, string T)
{
int i = ; // S 的下标
int j = ; // T的下标
int s_len = S.size(); // 字符串长度
int t_len = T.size();
if(s_len<t_len)
return -;
while (i < s_len && j < t_len)
{
if (S[i] == t[j]) // 相等,则都前进一步
{
i++;
j++;
}
else // 回溯
{
i = i - j + ;
j = ;
}
} if (j == t_len) // 匹配成功
return i - j; return -;
}
时间复杂度的范围是(最好与最坏情况):O(n+m)~O(n*m),其中,n为主串S的长度,m为模式串T的长度。
三、改进的模式匹配算法KMP
KMP算法的核心是如何求出next数组!next数组其实就是查找T中每一位前面的子串的前后缀有多少位匹配,从而决定j失配时应该回退到哪个位置(前后缀的概念请看附录)。
文字总是枯燥的,如果图文并茂,那就更好了!好了,上图!

这个图画的就是T这个要查找的关键字字符串。假设我们有一个空的next数组,我们的工作就是要在这个next数组中填值。
下面我们用数学归纳法来解决这个填值的问题。
这里我们借鉴数学归纳法的三个步骤(或者说是动态规划?):
1、初始状态
2、假设第j位以及第j位之前的我们都填完了
3、推论第j+1位该怎么填
初始状态我们稍后再说,我们这里直接假设第j位以及第j位之前的我们都填完了。也就是说,从上图来看,我们有如下已知条件:
next[j] == k;
next[k] == 绿色色块所在的索引;
next[绿色色块所在的索引] == 黄色色块所在的索引;
我们来看下面一个图,可以得到更多的信息:

1.由"next[j] == k;"这个条件,我们可以得到A1子串 == A2子串(根据next数组的定义,前后缀那个)。
2.由"next[k] == 绿色色块所在的索引;"这个条件,我们可以得到B1子串 == B2子串。
3.由"next[绿色色块所在的索引] == 黄色色块所在的索引;"这个条件,我们可以得到C1子串 == C2子串。
4.由1和2(A1 == A2,B1 == B2)可以得到B1 == B2 == B3。
5.由2和3(B1 == B2, C1 == C2)可以得到C1 == C2 == C3。
6.B2 == B3可以得到C3 == C4 == C1 == C2
接下来,我们开始用上面得到的条件来推导如果第j+1位失配时,我们应该填写next[j+1]为多少?
next[j+1]即是找T从0到j这个子串的最大前后缀:
#:(#:在这里是个标记,后面会用)我们已知A1 == A2,那么A1和A2分别往后增加一个字符后是否还相等呢?我们得分情况讨论:
(1)如果T[k] == T[j],很明显,我们的next[j+1]就直接等于k+1。用代码来写就是next[++j] = ++k;
(2)如果T[k] != T[j],那么我们只能从已知的,除了A1,A2之外,最长的B1,B3这个前后缀来做文章了。
那么B1和B3分别往后增加一个字符后是否还相等呢?
由于next[k] == 绿色色块所在的索引,我们先让k = next[k],把k挪到绿色色块的位置,这样我们就可以递归调用"#:"标记处的逻辑了。
由于j+1位之前的next数组我们都是假设已经求出来了的,因此,上面这个递归总会结束,从而得到next[j+1]的值。
我们唯一欠缺的就是初始条件了:next[0] = -1, k = -1, j = 0另外有个特殊情况是k为-1时,不能继续递归了,此时next[j+1]应该等于0,即把j回退到首位。
即 next[j+1] = 0; 也可以写成next[++j] = ++k;
接下来就是代码实现next数组(C++版):
int* getNext(string T)
{
int T_len = T.size();
int* next = new int[T_len]; // 声明next数组
int i = ; // T的下标
int j = -;
next[] = -;
while (i < T_len)
{
if (j == - || T[i] == T[j])
{
next[++i] = ++j;
}
else
j = next[j];
}
return next; }
KMP优化:
如果T[k] == T[j],很明显,我们的next[j+1]就直接等于k+1。用代码来写就是next[++j] = ++k;可是我们知道,第j+1位是失配了的,如果我们回退j后,发现新的j(也就是此时的++k那位)跟回退之前的j也相等的话,必然也是失配。所以还得继续往前回退。
int* getNext(string T)
{
int T_len = T.size();
int* next = new int[T.size()]; // 声明next数组
int i = ; // T的下标
int j = -;
next[] = -;
while (i < T_len)
{
if (j == - || T[i] == T[j])
{
if (T[i + ] == T[j + ]) //KMP优化
next[++i] = next[++j];
else
next[++i] = ++j;
}
else
j = next[j];
}
return next; }
完整代码C++:
#include <iostream>
#include <string>
using namespace std;
//获取next数组
int* getNext(string T)
{
int* next = new int[T.size()]; // 声明next数组
int T_len = T.size();
int i = ; // T的下标
int j = -;
next[] = -;
while (i < T_len)
{
if (j == - || T[i] == T[j])
{
if (T[i + ] == T[j + ]) //KMP优化
next[++i] = next[++j];
else
next[++i] = ++j;
}
else
j = next[j];
}
return next; } // KMP算法,在 S 中找到 T 第一次出现的位置
int KMP(string S, string T) // S为主串,T为模式串
{
int* next = getNext(T);
int i = ; // S下标
int j = ; // T下标
int s_len = S.size();
int t_len = T.size();
while (i < s_len && j < t_len)
{
if (j == - || S[i] == T[j]) //T 的第一个字符不匹配或S[i] == T[j]
{
i++;
j++;
}
else
j = next[j]; // 当前字符匹配失败,进行跳转
}
if (j == t_len) // 匹配成功
return i - j;
return -;
} int main()
{
string S = "bbc abcdab abcdabcdabde";
string T = "abcdabd";
int num = KMP(S, T);
cout << num<<endl;
system("pause");
return ;
}
附录:关于前后缀
来一张图片说明吧:

由上图所得, "前缀" 指除了自身以外,一个字符串的全部头部组合;"后缀" 指除了自身以外,一个字符串的全部尾部组合。
参考文献:
[1]王红梅, 胡明, 王涛. 数据结构(C++版)[M]. 北京:清华大学出版社, 2011:83-85.
[2]唐小喵的博客:http://www.cnblogs.com/tangzhengyue/p/4315393.html#3831240
字符串模式匹配之KMP算法的next数组详解与C++实现的更多相关文章
- KMP算法的Next数组详解
转载请注明来源,并包含相关链接. 网上有很多讲解KMP算法的博客,我就不浪费时间再写一份了.直接推荐一个当初我入门时看的博客吧:http://www.cnblogs.com/yjiyjige/p/32 ...
- KMP算法的Next数组详解 转
这个写的很好,还有讲kmp,值得一看. http://www.cnblogs.com/tangzhengyue/p/4315393.html 转载请注明来源,并包含相关链接. 网上有很多讲解KMP算法 ...
- KMP算法的Next数组详解(转)
转载请注明来源,并包含相关链接. 网上有很多讲解KMP算法的博客,我就不浪费时间再写一份了.直接推荐一个当初我入门时看的博客吧: http://www.cnblogs.com/yjiyjige/p/3 ...
- 字符串模式匹配之KMP算法图解与 next 数组原理和实现方案
之前说到,朴素的匹配,每趟比较,都要回溯主串的指针,费事.则 KMP 就是对朴素匹配的一种改进.正好复习一下. KMP 算法其改进思想在于: 每当一趟匹配过程中出现字符比较不相等时,不需要回溯主串的 ...
- KMP算法的优化与详解
文章开头,我首先抄录一些阮一峰先生关于KMP算法的一些讲解. 下面,我用自己的语言,试图写一篇比较好懂的 KMP 算法解释. 1. 首先,字符串"BBC ABCDAB ABCDABCDABD ...
- 串的模式匹配和KMP算法
在对字符串的操作中,我们经常要用到子串的查找功能,我们称子串为模式串,模式串在主串中的查找过程我们成为模式匹配,KMP算法就是一个高效的模式匹配算法.KMP算法是蛮力算法的一种改进,下面我们先来介绍蛮 ...
- 【转载】KMP入门级别算法详解--终于解决了(next数组详解)
[转载]https://blog.csdn.net/LEE18254290736/article/details/77278769 对于正常的字符串模式匹配,主串长度为m,子串为n,时间复杂度会到达O ...
- poj 2406:Power Strings(KMP算法,next[]数组的理解)
Power Strings Time Limit: 3000MS Memory Limit: 65536K Total Submissions: 30069 Accepted: 12553 D ...
- KMP算法的next[]数组通俗解释
原文:https://blog.csdn.net/yearn520/article/details/6729426 我们在一个母字符串中查找一个子字符串有很多方法.KMP是一种最常见的改进算法,它可以 ...
随机推荐
- Rational Rose2007下载和安装
网上关于Rational Rose2007安装包,网上找了一堆大多都是垃圾,最后找到一个可用的(带激活文件),保存在自己的网盘里,这里分享出来:https://pan.baidu.com/s/1bpb ...
- java NIO学前准备
之前一直对NIO感兴趣,无奈对IO的很多概念很模糊,所以对于NIO的学习也是一直半解,最近在网上查阅了很多资料,发现有很多概念是需要反复理解的,有的时候甚至当时理解了,但一段时间后又忘记了,所以决定自 ...
- SqlHelper中SqlHelperParameterCache类的用法介绍
SqlHelper类中提供了三种可以用来管理SqlParameter参数的共享方法.下面来一一讲解: 1.CacheParameterSet 将SqlParameter参数数组存储到本地缓存中 2.G ...
- 【Lua】LWT后台用JSON与 ExtJS传递数据
要完成目录树的构建,需要前台ExtJS构筑页面,后台处理逻辑,中间由JSON传递数据. 首先搭建后台环境: require "httpd" require "lfs&qu ...
- 转 Unity企业级支持案例与分析
Unity大中华区技术支持总监张黎明以“Unity企业级支持案例与分析”为主题进行了分享. 以下为演讲实录: 张黎明:非常感谢大家来参加今年的Unite,其实我现在看到有的朋友已经不是第一次来参加Un ...
- ACS蚁群算法求解对称TSP旅行商问题的JavaScript实现
本来以为在了解蚁群算法的基础上实现这道奇怪的算法题并不难,结果实际上大相径庭啊.做了近三天时间,才改成现在这能勉强拿的出手的模样.由于公式都是图片,暂且以截图代替那部分内容吧,mark一记. 1 蚁群 ...
- chmod修改文件的权限/chown修改文件和目录的所有者(转)
ll指令的显示的信息为(当前目录下只有nameservice1一个目录): drwxr-xr-x 3 hdfs hdfs 4096 4月 14 16:19 nameservice1 上述信息分别表示: ...
- ASP.NET MVC4 新手入门教程之六 ---6.编辑视图与编辑方法
在本节中,您会为电影控制器检查生成的操作方法和视图.然后,您将添加一个自定义的搜索页面. 运行该应用程序,然后浏览到Movies控制器通过将/Movies追加到您的浏览器的地址栏中的 URL.将鼠标指 ...
- css常用左右布局方案整理
实际项目开发过程中我们经常会遇到页面div左右布局的需求:左侧 div 固定宽度,右侧 div 自适应宽度,填充满剩余页面,下面整理几种常用的方案 1 左侧 div 设置 float 属性为 le ...
- 插入排序——Python实现
一.排序思想 排序思想参见:https://www.cnblogs.com/luomeng/p/10583124.html 二.python实现 def InsertSort(arrs): " ...