关于KMP中求next数组的思考【转】
文章转自 http://www.tuicool.com/articles/yayeIbe。这是我看到关于求next数组,解释最好的一篇文章!!!!!!!
KMP的next数组求法是很不容易搞清楚的一部分,也是最重要的一部分。我这篇文章就以我自己的感悟来慢慢推导一下吧!保证你看完过后是知其然,也知其所以然。
如果你还不知道KMP是什么,请先阅读上面的链接,先搞懂KMP是要干什么。
下面我们就来说说KMP的next数组求法。
KMP的next数组简单来说,假设有两个字符串,一个是待匹配的字符串strText,一个是要查找的关键字strKey。现在我们要在strText中去查找是否包含strKey,用i来表示strText遍历到了哪个字符,用j来表示strKey匹配到了哪个字符。
如果是暴力的查找方法,当strText[i]和strKey[j]匹配失败的时候,i和j都要回退,然后从i-j的下一个字符开始重新匹配。
而KMP就是保证i永远不回退,只回退j来使得匹配效率有所提升。它用的方法就是利用strKey在失配的j为之前的成功匹配的子串的特征来寻找j应该回退的位置。而这个子串的特征就是前后缀的相同程度。
所以next数组其实就是查找strKey中每一位前面的子串的前后缀有多少位匹配,从而决定j失配时应该回退到哪个位置。
我知道上面那段废话很难懂,下面我们看一个彩图:

这个图画的就是strKey这个要查找的关键字字符串。假设我们有一个空的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]即是找strKey从0到j这个子串的最大前后缀:
#:(#:在这里是个标记,后面会用)我们已知A1 == A2,那么A1和A2分别往后增加一个字符后是否还相等呢?我们得分情况讨论:
(1)如果str[k] == str[j],很明显,我们的next[j+1]就直接等于k+1。
用代码来写就是next[++j] = ++k;
(2)如果str[k] != str[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;
public static int[] getNext(String ps)
{
char[] strKey = ps.toCharArray();
int[] next = new int[strKey.length];
// 初始条件
int j = 0;
int k = -1;
next[0] = -1;
// 根据已知的前j位推测第j+1位
while (j < strKey.length - 1)
{
if (k == -1 || strKey[j] == strKey[k])
{
next[++j] = ++k;
}
else
{
k = next[k];
}
}
return next;
}
现在再看这段代码应该没有任何问题了吧。
优化:
细心的朋友应该发现了,上面有这样一句话:
(1)如果str[k] == str[j],很明显,我们的next[j+1]就直接等于k+1。 用代码来写就是next[++j] = ++k;
可是我们知道,第j+1位是失配了的,如果我们回退j后,发现新的j(也就是此时的++k那位)跟回退之前的j也相等的话,必然也是失配。所以还得继续往前回退。
public static int[] getNext(String ps)
{
char[] strKey = ps.toCharArray();
int[] next = new int[strKey.length];
// 初始条件
int j = 0;
int k = -1;
next[0] = -1;
// 根据已知的前j位推测第j+1位
while (j < strKey.length - 1)
{
if (k == -1 || strKey[j] == strKey[k])
{
// 如果str[j + 1] == str[k + 1],回退后仍然失配,所以要继续回退
if (str[j + 1] == str[k + 1])
{
k = next[k + 1];
next[++j] = k;
}
else
{
k = k + 1;
next[++j] = k;
}
}
else
{
k = next[k];
}
}
return next;
}
好了,自此KMP的next求法全部讲解完毕。欢迎大家指出文章的错误,我好更加完善它。
----------------------------------------------------------------------------------------------------------
下面说说面试的时候 ,给一个字符串,要你写出它的Next数组,应该怎么写:


①:先对每一位左边的子串求出最大前后缀串的长度,作为初始的Next数组
②:因为第一位失配时需要移动i,因此赋值为-1
③:P[3] == A, Next[3] == 0, P[0] == A; 所以P[3] == P[0], (移动过去后还是失配,需要继续移动),优化Next[3]为Next[0],即-1
④:同理优化Next[10]为Next[0],即-1
⑤:同理优化P[14],P[15],P[16]
关于KMP中求next数组的思考【转】的更多相关文章
- KMP算法中求next数组的实质
在串匹配模式中,KMP算法较蛮力法是高效的算法,我觉得其中最重要的一点就是求next数组: 看了很多资料才弄明白求next数组是怎么求的,我发现我的忘性真的比记性大很多,每次看到KMP算法求next数 ...
- 深入理解kmp中的next数组
next数组 1. 如果对于值k,已有p0 p1, ..., pk-1 = pj-k pj-k+1, ..., pj-1,相当于next[j] = k. 此意味着什么呢?究其本质,next[j] = ...
- KMP算法求next数组
next数组的求解方法是:第一位的next值为0,第二位的next值为1.后面求解每一位的next值时,根据前一位进行比较.首先将前一位与其next值对应的内容进行比较,如果相等,则该位的next值就 ...
- Seek the Name, Seek the Fame---poj2752(kmp中的Next数组)
题目链接:http://poj.org/problem?id=2752 题意就是求出是已知s串的前缀的长度x,并且要求此前缀也是s串的后缀:求出所有的 x : Next[i]的含义是前i个元素的前缀和 ...
- POJ 2752 KMP中next数组的理解
感觉这里讲的挺好的.http://cavenkaka.iteye.com/blog/1569062 就是不断递归next数组.长度不断减小. 题意:给你一个串,如果这个串存在一个长度为n的前缀串,和长 ...
- 对于kmp求next数组的理解
首先附上代码 1 void GetNext(char* p,int next[]) 2 { 3 int pLen = strlen(p); 4 next[0] = -1; 5 int k = -1; ...
- KMP 算法中的 next 数组
KMP 算法中对 next 数组的理解 next 数组的意义 此处 next[j] = k:则有 k 前面的浅蓝色区域和 j 前面的浅蓝色区域相同: next[j] 表示当位置 j 的字符串与主串不匹 ...
- 求一个数组中最小的K个数
方法1:先对数组进行排序,然后遍历前K个数,此时时间复杂度为O(nlgn); 方法2:维护一个容量为K的最大堆(<算法导论>第6章),然后从第K+1个元素开始遍历,和堆中的最大元素比较,如 ...
- 编写函数求整形数组a中存储的m个不重复的整数的第k大的整数(其中m>=1,1<=k<=m)很简单的一个思路是酱紫的:管他辣么多干啥,上来一把排序然后直接得答案
/** * @author:(LiberHome) * @date:Created in 2019/2/28 20:38 * @description: * @version:$ *//*编写函数求整 ...
随机推荐
- MongoDB系列一:MongoDB文档型数据库特点介绍
一.MongoDB数据库的存储特点: 1.MongoDB是文档型数据库,存储的是文档(将json二进制化为Bson存储): 2.存储的每篇文档都可以有自己独特的存储结构,颠覆了传统数据库必须按照表和列 ...
- 解决Android Studio 升级时提示 Connection failed. Please check your network connection and try again问题
一,问题: 无论mac还是windows可能都会出现这个问题,解决方案大同小异,就是修改VMOptions而已. 解决方案: Windows: 在\Android Studio\bin目录下找到 st ...
- 用key管理可复用元素
先看看不用key管理可复用元素的代码.Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染.这么做,除了使 Vue 变得非常快之外,还有一些有用的好处,那就是在切换input时不会清楚 ...
- Git入门之在IDEA中使用Git上传maven项目
下载安装git客户端: 参考博文:https://www.cnblogs.com/java-maowei/p/5950930.html 在IDEA怎么使用git上传spring的maven项目详解: ...
- 010-数据结构-树形结构-B树[B-树]
一.概述 B 树就是常说的“B 减树(B- 树)”,又名平衡多路(即不止两个子树)查找树. 在计算机科学中,B树(英语:B-tree)是一种自平衡的树,能够保持数据有序.这种数据结构能够让查找数据.顺 ...
- 006-top查看计算机信息
1.使用top命令基本信息 us:用户态使用的cpu时间比sy:系统态使用的cpu时间比ni:用做nice加权的进程分配的用户态cpu时间比id:空闲的cpu时间比wa:cpu等待磁盘写入完成时间hi ...
- Python-sympy科学计算与数据处理(数学表达式)
数学表达式 from sympy import * 1/2+1/3 S(1)/2+1/S(3) Out[4]: 5/6 Rational(5,10) Out[5]: 1/2 x,y = symbols ...
- spring boot系列(七)spring boot 使用mongodb
1 pom.xml配置 增加包依赖:spring-boot-starter-data-mongodb <dependency> <groupId>org.springframe ...
- 如何使用delphi将Clientdataset的Delta保存到数据库中
[delphi] view plain copy //ATableName-表名, AKeyField-主键,多个主键用;隔开,如 ;pid;times; from:unit HlsImplBase; ...
- Code First EF 多对多时拆分两个一对多
(*)多对多中还可以为中间表建立一个实体方式映射.当然如果中间关系表还想有其他字段,则要必须为中间表建立实体类(中间表和两个表之间就是两个一对多的关系了). demo项目 WebApp22 GitHu ...