KMP算法
KMP算法是一种高效的字符串匹配算法,传统的基于暴力匹配的字符串匹配算法的时间复杂度为O(M*N),而KMP算法通过在匹配过程中在子串引入前缀和后缀最大匹配长度,大大较少了字符串匹配的次数,时间复杂度为O(M+N)。
字符串前缀后缀最大匹配长度数组next的获取
KMP算法的其中一个关键就是获取子串每个位置的前缀后缀最大匹配长度,假设子串的s2为"abbsabbecabbsabbdf",规定
next[0] = -1
next[1] = 0
next[2]表示数组第三个元素之前即s2[:2]的前缀后缀最大匹配长度。
s2[:2] = “ab”
'a’和’b’不相等,因此next[2] = 0
……
s2[:7] = "abbsabbe"中前缀和后缀相同且长度最大的字符子串为"abb"因此next[7] = 3.
……
依次类推
s2[:17] = "abbsabbecabbsabbd"中前缀和后缀相同且长度最大的字符子串为abbsabb"依次next[17] = 7。
以上为next数组的具体求法,但在实际应用中是怎么实现的呢?
在next数组的求解中发现,next数组的第i个位置上的值和第i-1个位置上的值有关。如下所示:
假设子串s2 = “abbstabbecabbstabb”,子串第i个位置上的next数组值等于:如果第s2[next[i-1]]位置上的值等于s2[i-1]的值,则next[i] = next[i-1]+1;如果不等于,则next数组继续向前跳,判断s2[next[next[i-1]]]是否等于s2[i-1],直到相等的值,或者满足终止条件为止。
代码实现如下:
public static int[] getNextArray(char[] chs) {
if (chs.length == 1) {
return new int[] {-1};
}
int[] next = new int[chs.length];
next[0] = -1;
next[1] = 0;
int index = 2;
int cn = 0;
while (index < chs.length) {
if (chs[index - 1] == chs[cn]) {// 判断i-1位置的数是否和next数组中的值相等,如果相等则当前next里的值为上一次的值+1
next[index++] = ++cn;
} else if (cn > 0) { // 如果不相等继续向前跳,直到cn小于等于0,说明其没有前缀等于后缀的长度了
cn = next[cn];
} else { // 此时说明跳不了了,即该位置上没有前缀和后缀相匹配的字符串
next[index++] = 0;
}
}
return next;
}
KPM算法实现
public static int getKMPIndex(String s1, String s2) {
if (s1 == null || s2 == null || s2.length() < 1 || s1.length() < s2.length()) {
return -1;
}
char[] str1 = s1.toCharArray();
char[] str2 = s2.toCharArray();
int index1 = 0;
int index2 = 0;
int[] next = getNextArray(str2);
while (index1 < str1.length && index2 < str2.length) {
if (str1[index1] == str2[index2]) {
index1++;
index2++;
} else if (index2 == 0) { // next[index2] = -1,str2中比对的位置已经没有办法往前跳了。
index1++;
} else {
index2 = next[index2];
}
}
// 判断index2越界还是index1越界
return index2 == str2.length ? index1 - index2 : -1;
}
算法分析:在交接了next数组的求法之后,接下来KMP算法的理解就更加容易了。首先声明主串的指针和子串的指针,然后比较主串指针对应的字符和子串指针对应的字符是否相等,如果相等则两个指针分别向下移动,如果不相等则子串指针跳转到子串指针的next数组,相当于将最大前缀移动到了待比较的位置,然后再次比较主串指针对应的字符和子串指针对应字符是否相等,然后重复上述过程,直到满足终止条件,当满足终止条件后,如果子串指针越界则说明匹配到了字符,否则没有匹配到字符。