关于KMP,是算法中的基础算法,学了半天还是一知半解,在此笔者把所知的,尽量写下来。
-
初认KMP,字符串匹配算法。
- KMP一般用于一个文本串A内,查找一个子串B出现的位置。
- 代码中用方法实现的话,就是在字符串"abcdef"中查找“def”的位置,成功返回下标4。
-
写KMP代码关键点(利用next数组加速匹配)
-
next数组,主要是记录字符串中每个字符的前后最大匹配长度。
-
利用单个字符最长前后缀相等的长度,来填充next数组。
- 比如,算字符d的最长前后缀匹配长度,理论上计算前缀时不取最后一个字符,后缀不取第一个字符;比如,d字符前面的串"abcabc",前缀最长为"abcab",后缀最长为"bcabc"
- 第一次,a != c。第二次,ab != bc。第三次,abc == abc。第四次,abca != abca。
- 以此类推。所以,最后字符d的前后缀匹配长度为3。
- 规定,下标为0和1的值分别为-1和0
-
最后,字符串B会生成对应的next数组,如下:
-
-
接下来,笔者想展示KMP如何加速字符串匹配过程:
-
先看看暴力匹配的思路:字符串A不动,匹配串B依次向后匹配。
-
利用next数组,进行加速的KMP过程:
-
预设最长前后缀相等长度为:v
-
预设字符串A :“abcababcabe…(省略)”,字符串B:“abcababcabf”,在A中匹配B。
-
接下来是关于代码实现,主要是边界的控制与合理的跳跃比较。
-
-
KMP算法代码实现:
-
KMP主逻辑代码并不复杂,主要是下标变换:
- 定义指针i1和i2分别指向对应的字符串。如下图,当遇到不等的字符时,即e != f 。这是,i1位置不变,i2从下标为10,变为下标为5,继续比较。
- 那么怎么变?next数组起作用了,字符f对应的next值为5,所以直接把next的值赋给i2即可。因为next的值就代表前或后缀的长度。
-
贴上主逻辑代码,如何求next数组后面再讲。
/** * @param s1 原始串 * @param s2 匹配串 * @return */ public static int getIndexOf(String s1, String s2) { // 参数检查 // 两者非空 || s2没有内容 || s2长度大于s1 if(s1 == null || s2 == null || s2.length() < 1 || s1.length() < s2.length()) { return -1; } // 变为char数组 char[] s = s1.toCharArray(); char[] m = s2.toCharArray(); // i1指向s1 int i1 = 0; // i2指向s2 int i2 = 0; // next长度与m相同 int[] next = getNext(m); while(i1 < s.length && i2 < m.length) { // 当值相等的时候, 继续匹配下一个字符 if(s[i1] == m[i2]) { i1++; i2++; } else { // 不等, 使用KMP // 判断next数组,i2来到s下标为0的位置, s已经无法再向前跳 if(next[i2] == -1) { // 从next数组可以看出m已经到达下标为0的位置 // 说明s和m, 连第一个字符都无法匹配 i1++; } else { // i2跳到相应的位置 i2 = next[i2]; } } } // 循环跑完, i2的下标跑完数组m, 则表示已经成功匹配 return i2 == m.length ? i1 - i2 : -1; }
-
next数组可以由数学归纳求得:
-
取前缀后一个字符,与当前位置的上一个位置进行比较。
-
代码实现,注释较多,就不过多叙述了。
public static int[] getNext(char[] m) { // 参数检查 if(m.length == 1) { return new int[] {-1}; } int[] next = new int[m.length]; // 设置初始值 next[0] = -1; next[1] = 0; // 0-1已经预设, 接下来从位置2 int i = 2; int cn = 0; while(i < next.length) { // 当前前一个位置与跳跃的位置相等 if(m[i - 1] == m[cn]) { // 注意++的位置 next[i++] = ++cn; } else if(cn > 0) { // m[i - 1] != m[cn] // 且 cn > 0, 说明cn还可以往前跳跃 cn = next[cn]; } else { // cn <= 0, 说明i位置next值为0 next[i++] = 0; } } return next; }
-
-