字符串匹配KMP算法 (关于部分匹配数组的获取! 已发现方法有误)
2020/11/24 更新:已经知道自己错在哪里了,这种方法确实是不对的
对于aabaaa 这个字符串 PMT应该是010122而我的方法就变成了010121
留作纪念吧哈哈哈哈
KMP算法的两种实现方式:Next数组与PMT表,这两天一直纠结Next数组的获取方式,现在把自己的思路写在这里,方便以后复习,也欢迎大家指出错误!这里主要是讨论部分匹配数组或是Next数组的获取的一个细节,关于算法的内容这篇文章写的很详细啦:
https://www.cnblogs.com/zzuuoo666/p/9028287.html
KMP算法的实质:就是在主串匹配模式串失败的时候,不用从头再开始,而是根据已有的信息,回溯到对应的位置
两种方式(本质上都是一样的,只是使用方式有所不同):
①next 数组: next数组每个坐标代表了,这个坐标之前的字符串的前后缀最大公共子串长度
②部分匹配数组: 它代表了从第一个到这一个所有字符串的前后缀最大公共子串长度
字符串数组 | A | B | C | D | A | B | C | D | A | B | D | A |
---|---|---|---|---|---|---|---|---|---|---|---|---|
位置 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
next数组 | -1 | 0 | 0 | 0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 0 |
部分匹配表(PMT) | 0 | 0 | 0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 0 | 1 |
从上表可以看出,如果能够获取部分匹配数组,next数组就是把部分匹配数组向右移动一位,并把next[0]=1
next[i]就代表了,回溯的时候要回溯的位置即i=next[i]
而部分匹配数组中回溯的值是i=PMT[i -1]
在获取数组中的一个注意点,以PMT为例,很多教程在PMT[j] != PMT[i]的时候, j= PMT[j-1] ,这是KMP的核心,但是!!!,在求子串的PMT或是NEXT数组的时候,我认为是没有必要的 以PMT数组为例,相当于上表中匹配到第10位置(从0开始),
ABCDABCDAB已经是匹配完成的了,且到最后一个B的时候,这个串的前后缀公共长度是6
-
分别是前缀:ABCDABCDAB后缀ABCDABCDAB,所以接下来匹配的是“新加入的”PMT[10] 与 C 很明显 PMT[10] = D != C
-
一般情况下, 此时j = PMT[10 - 1]=PMT[9] = 6 实际上是比较ABCDABC 和ABCDABD是否相等,因为ABCDABCDAB后缀ABCDABCDAB已经相等了!只要这个新的相等,那么长度就继续增加,如果不等,那么尝试看看**有没有更短的前缀能匹配 ** 而而PMT[6] = C !=D所以这个串不可能再延长了,回溯! j = PMT[6-1]
-
那么再回溯 j = PMT[6-1]=PMT[5] = 2 此时实际上是找到一个更段的公共部分 ABCDABCDABD 比较第一个AB后面的字符 和当前AB后面的D是否相等 ,如果相等 则 PMT[10] = 3 ,否则再回溯 而 PMT[2] = C != D继续回溯 j = PMT[2-1] = PMT[1] = 0 PMT[0]= A 此时就是看新加入的 D 能否和 最开始的A形成公共部分
此时比较 A 与 D 很显然不相等 所以 PMT[10]= 0
从上述的推断中,可以看出,除了回到字符串首位的情况,其余的回溯过程,取的都是同样的值!,也就是说,除了回溯到句首的情况,只要出现回溯的情况,每次都是比较相同的字符。这是由于PMT数组的性质决定的,也正因为这个特性 ,在匹配主串的时候能够提高效率,但是在模式串自匹配的时候就显得很多余。
一旦出现不匹配,且可以回溯的情况,其实只要考虑两种
- 如果当前的字符j和 str[0] 相等,则长度为PMT[j]=1
- 如果不等,长度为0 PMT[j] = 0
而在匹配主串的时候,则要按照j=PMT[j-1]的规则,否则就变成了暴力匹配!对于next数组也是一样的情况,只是部分匹配表更加直观一点!!
实际上这两个的本质是一样的!
分别用这两种方式实现KMP:
//第一种,采用PMT数组,我认为是比较容易理解和记忆的一种
public class KMP {
public static void main(String[] args) {
String str1 = "BBC A ABCDABCDABD";
String str = "ABCDABD";
int[] kmpNext = getKMPNext(str);
System.out.println(Arrays.to