https://www.zhihu.com/question/21923021
https://www.jianshu.com/p/6c53d06d23cc
关键:理解PMP[j] 或者 next[j] 的含义:
模式串的子串[0-j]的最长公共前后缀的长度。
关键是:求模式串的PMT表:求模式串中每个索引处对应的子串的最大前后缀交集的长度。得到PMT后,为了编码方便,再把PMT的值依次后移,得到Next数组.
前缀、后缀:都是从左往右,不包括字符串本身。
例如模式串ABCDABD
子串有:A, AB, ABC, ABCD, ABCDA, ABCDAB, ABCADABD
其中的子串ABCDA对应的PMT值为1,因为
前缀有:{A, AB, ABC, ABCD}
后缀有:{A, DA, CDA, BCDA}
最长的前后缀的交集是: A,所以PMT值是1
最终模式串的PMT为:0000120, 右移求Next表: -1,0,0,0,0,1,2
- 当进行匹配时,在模式串的j 处, 原始串的i 处失效时,代表模式串的前j - 1的子串 与 原始串[i-(j-1), i-1] 是相同的。 此时看PMT表中j处的值PMP[j] 【即模式子串[0-j]的最长前后缀长度】, 原始串i不动,模式串j 跳到索引为PMT[j] 处,重新进行匹配。
- 为什么可以直接跳到模式串的PMT[j]处继续匹配?【关键是理解PMT[j]的含义】
假设PMT[j] == 2, 它的意思是模式子串s[0-j]的最大前后缀的交集是2,原始字符串失配处i 之前的2个作为后缀,模式串s[0-2]作为前缀,无需重复比较,直接从原始串i处 与 模式子串s[2]开始比较。
分别求出字串中每个字符的k值, 叫做字串的《部分匹配表》(Partial Match Table);
i: 子串的长度
不匹配时向前移动的步数 = 子串已匹配的字符数 - 对应的部分匹配值
当前字符是否与头部相同,相同的话k++
https://www.cnblogs.com/grandyang/p/6992403.html
前缀、后缀都是从左往右的顺序
由于模式串的尾部可能有重复的字符,所以我们可以得出一个重要的结论:失配时,模式串向右移动的距离 = 已匹配字符数- 失配字符的上一位字符所对应的最大长度值[最大前缀后缀公共元素的长度]
我们之前是在字符'D'处失配的,上一位字符是'B',对应的最大长度是2,此时已经成功匹配了6个字符,那么我们就将模式串向右移动6-2=4位,并继续匹配即可。
上面的中间那行是之前的最大前缀后缀公共元素长度数组,我们将其整体右移一位,多出的位置补上一个-1,就变成了下面的一行。那么我们此时就直接找失配字符的next值就行了。于是我们就得到了新的结论:失配时,模式串向右移动的距离 = 失配字符所在位置 - 失配字符对应的next值。
我们知道如果模式串当前位置j之前有k个相同的前缀后缀,那么可以表示为next[j] = k,所以如果当模式串的p[j]跟文本串失配后,我们可以用next[j]处的字符继续和文本串匹配【next[j]个的前后缀相同,无需比较】,相当于模式串向右移动了j - next[j]位。
next数组,当前位的值代表当前位前N位与模式串开头的前N位相同。
https://www.zhihu.com/question/21923021
PMT中的值是字符串的前缀集合与后缀集合的交集中最长元素的长度。
如果在 j 处字符不匹配,那么由于前边所说的模式字符串 PMT 的性质,主字符串中 i 指针之前的 PMT[j −1] 位就一定与模式字符串的第 0 位至第 PMT[j−1] 位是相同的。这是因为主字符串在 i 位失配,也就意味着主字符串从 i−j 到 i 这一段是与模式字符串的 0 到 j 这一段是完全相同的。
我们看到如果是在 j 位 失配,那么影响 j 指针回溯的位置的其实是第 j −1 位的 PMT 值,所以为了编程的方便, 我们不直接使用PMT数组,而是将PMT数组向后偏移一位。我们把新得到的这个数组称为next数组。下面给出根据next数组进行字符串匹配加速的字符串匹配程序。其中要注意的一个技巧是,在把PMT进行向右偏移时,第0位的值,我们将其设成了-1,这只是为了编程的方便,并没有其他的意义。
========================================
链接:https://www.zhihu.com/question/21923021/answer/281346746
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
有些算法,适合从它产生的动机,如何设计与解决问题这样正向地去介绍。但KMP算法真的不适合这样去学。最好的办法是先搞清楚它所用的数据结构是什么,再搞清楚怎么用,最后为什么的问题就会有恍然大悟的感觉。我试着从这个思路再介绍一下。大家只需要记住一点,PMT是什么东西。然后自己临时推这个算法也是能推出来的,完全不需要死记硬背。
KMP算法的核心,是一个被称为部分匹配表(Partial Match Table)的数组。我觉得理解KMP的最大障碍就是很多人在看了很多关于KMP的文章之后,仍然搞不懂PMT中的值代表了什么意思。这里我们抛开所有的枝枝蔓蔓,先来解释一下这个数据到底是什么。
对于字符串“abababca”,它的PMT如下表所示:

就像例子中所示的,如果待匹配的模式字符串有8个字符,那么PMT就会有8个值。
我先解释一下字符串的前缀和后缀。如果字符串A和B,存在A=BS,其中S是任意的非空字符串,那就称B为A的前缀。例如,”Harry”的前缀包括{”H”, ”Ha”, ”Har”, ”Harr”},我们把所有前缀组成的集合,称为字符串的前缀集合。同样可以定义后缀A=SB, 其中S是任意的非空字符串,那就称B为A的后缀,例如,”Potter”的后缀包括{”otter”, ”tter”, ”ter”, ”er”, ”r”},然后把所有后缀组成的集合,称为字符串的后缀集合。要注意的是,字符串本身并不是自己的前后缀。
有了这个定义,就可以说明PMT中的值的意义了。PMT中的值是字符串的前缀集合与后缀集合的交集中最长元素的长度。例如,对于”aba”,它的前缀集合为{”a”, ”ab”},后缀 集合为{”ba”, ”a”}。两个集合的交集为{”a”},那么长度最长的元素就是字符串”a”了,长 度为1,所以对于”aba”而言,它在PMT表中对应的值就是1。再比如,对于字符串”ababa”,它的前缀集合为{”a”, ”ab”, ”aba”, ”abab”},它的后缀集合为{”baba”, ”aba”, ”ba”, ”a”}, 两个集合的交集为{”a”, ”aba”},其中最长的元素为”aba”,长度为3。
好了,解释清楚这个表是什么之后,我们再来看如何使用这个表来加速字符串的查找,以及这样用的道理是什么。如图 1.12 所示,要在主字符串"ababababca"中查找模式字符串"abababca"。如果在 j 处字符不匹配,那么由于前边所说的模式字符串 PMT 的性质,主字符串中 i 指针之前的 PMT[j −1] 位就一定与模式字符串的第 0 位至第 PMT[j−1] 位是相同的。这是因为主字符串在 i 位失配,也就意味着主字符串从 i−j 到 i 这一段是与模式字符串的 0 到 j 这一段是完全相同的。而我们上面也解释了,模式字符串从 0 到 j−1 ,在这个例子中就是”ababab”,其前缀集合与后缀集合的交集的最长元素为”abab”, 长度为4。所以就可以断言,主字符串中i指针之前的 4 位一定与模式字符串的第0位至第 4 位是相同的,即长度为 4 的后缀与前缀相同。这样一来,我们就可以将这些字符段的比较省略掉。
具体的做法是,保持i指针不动,然后将j指针指向模式字符串的PMT[j −1]位即可。
简言之,以图中的例子来说,在 i 处失配,那么主字符串和模式字符串的前边6位就是相同的。又因为模式字符串的前6位,它的前4位前缀和后4位后缀是相同的,所以我们推知主字符串i之前的4位和模式字符串开头的4位是相同的。就是图中的灰色部分。那这部分就不用再比较了。

有了上面的思路,我们就可以使用PMT加速字符串的查找了。我们看到如果是在 j 位 失配,那么影响 j 指针回溯的位置的其实是第 j −1 位的 PMT 值,所以为了编程的方便, 我们不直接使用PMT数组,而是将PMT数组向后偏移一位。我们把新得到的这个数组称为next数组。下面给出根据next数组进行字符串匹配加速的字符串匹配程序。其中要注意的一个技巧是,在把PMT进行向右偏移时,第0位的值,我们将其设成了-1,这只是为了编程的方便,并没有其他的意义。在本节的例子中,next数组如下表所示。

具体的程序如下所示:
int KMP(char * t, char * p) { int i = 0; //都是从0开始 int j = 0; while (i < strlen(t) && j < strlen(p)) //O(m+n), 各自遍历 { if (j == -1 || t[i] == p[j]) //如果是从模式串的第一位开始,或者两者匹配,++i, ++j { i++; j++; } else //失配,主串 i不动,模式串跳到最大前后缀处,即PMP[j失配索引] j = next[j]; } if (j == strlen(p)) //匹配成功 return i - j; else return -1; }
好了,讲到这里,其实KMP算法的主体就已经讲解完了。你会发现,其实KMP算法的动机是很简单的,解决的方案也很简单。远没有很多教材和算法书里所讲的那么乱七八糟,只要搞明白了PMT的意义,其实整个算法都迎刃而解。
现在,我们再看一下如何编程快速求得next数组。其实,求next数组的过程完全可以看成字符串匹配的过程,即以模式字符串为主字符串,以模式字符串的前缀为目标字符串,一旦字符串匹配成功,那么当前的next值就是匹配成功的字符串的长度。
具体来说,就是从模式字符串的第一位(注意,不包括第0位)开始对自身进行匹配运算。 在任一位置,能匹配的最长长度就是当前位置的next值。如下图所示。





求next数组值的程序如下所示:
void getNext(char * p, int * next) //跟上面的KMP框架类似,主串是模式串,模式串是自己的子串 { next[0] = -1; //第一位-1, 不参与比较 int i = 0, j = -1; while (i < strlen(p)) { if (j == -1 || p[i] == p[j]) //j == -1时,直接++i, ++j, 越过第一位。 { ++i; ++j; next[i] = j; } else //失配的时候,从模式串[0-j]的最大公共前后缀处继续比较 j = next[j]; } }
至此,KMP算法就全部介绍完了。
==========================
4. next数组
前缀和后缀的最长公共序列,只和模式串有关,是模式串本身的特征。
所以,我们就可以事先算好模式串前n个字符的前缀和后缀的最长公共序列的长度,
把它们存起来,称为next数组。
对于模式串agctagcagctagct
来说,
它的next数组为[0,0,0,0,1,2,3,1,2,3,4,5,6,7,4],
即,模式串的前i+1个字符的前缀和后缀的最长公共序列的长度为next[i]。
例如:agctagcagctagct
的前6个字符agctag
的前缀和后缀的最长公共序列的长度为next[5]=2。
怎样计算这个数组呢?
我们可以利用next[0]~next[i]来计算next[i+1]。
假设pattern='agctagcagctagct'
a
:next[0]=0,
ag
:pattern[1]=g
,pattern[0]=a
,不相等,所以next[1]=0,
agc
:pattern[2]=c
,pattern[0]=a
,不相等,所以next[2]=0,
agct
:pattern[3]=t
,pattern[0]=a
,不相等,所以next[3]=0,
agcta
:pattern[4]=a
,pattern[0]=a
,相等,所以next[4]=1,
agctag
:pattern[5]=g
,pattern[1]=g
,相等,所以next[5]=2,
agctagc
:pattern[6]=c
,pattern[2]=c
,相等,所以next[6]=3,
……
agctagcagctagc
:pattern[13]=c
,pattern[6]=c
,相等,所以next[13]=7

5. 次长公共序列
难点来了。
agctagcagctagct
:pattern[14]=t
,pattern[7]=a
,不相等。
怎么办?于是next[14]=0吗?
很显然不行,因为agct
是前缀和后缀的最长共同序列,next[14]=4。

寻找agct
基于以下考虑,
如图,橙色的A表示已经确定的最长公共序列,绿色的T将要与开头的A后面的元素进行比较。
如果比对失败,我们需要寻找次长公共序列B,然后T再与开头的B后面元素进行比对。

我们看到,三幅图中,橙色块都是相等的,
如果存在次长公共序列,第二幅图表明,橙色块必然同时以B开头且以B结尾。【就是橙色块的最长公共序列长度】
即,如第三幅图所示,
这表明,T位置的次长公共序列长度,就是橙色块的最长公共序列长度。
因此,计算agctagcagctagc
的次长公共序列,就要计算B=agctagc
的最长公共序列,
而这个已经计算过了,next[6]=3,得到agc
。
然后与agc
后面的元素t
进行比对,相等,next[14]=4。
===理解上图===
关键:理解PMP[j] 或者 next[j] 的含义:
模式串的子串[0-j]的最长公共前后缀的长度。
图1:
A1后面的字符 与 A2后面的字符 T 不匹配了,此时从头开始【即设置j = 0 】当然也可以,不过就没有利用历史结果next数组。
可以尝试在A1和A2中找到一个次长的公共前后缀。即A1的头部 和 A2的尾部 相同的子串。
因为A1 == A2, 也即 要求出A1串的最长前后缀的长度,即PMP[last_index_of_A1], 即next[j]