初试KMP算法
KMP算法是一种改进的字符串匹配算法,是利用已经匹配过的字符串信息,尽量减少模式串与主串的匹配次数。在匹配失败时,不用每次都从模式串的第一个字符开始匹配,而是可以利用已经求得的最长公共前缀去匹配它的下一个字符,时间复杂度为O(m+n)。 而暴力算法在匹配失败时,每次都从模式串的第一个字符开始重新匹配,完全没有利用已经匹配过字符串的信息,效率比较低,时间复杂度为O(m*n)。
为了更好的理解KMP算法,可以分三步。
首先,先求模式串的next数组,这个数组存储的就是从模式串的第一个字符到该下标的最长公共前后缀长度。
len为已经匹配成功的最长前缀的长度,用它在数组中当作下标,正好可以当作要增加最长前缀的长度时所要匹配的下一个字符,如果满足条件就可以把这个长度加1并赋值给next[i]。不满足条件就要把len设为next[len-1],去利用它前一个字符的最长前缀长度,若是还不匹配,就再去找它的前一个字符,直到找到第一个字符为止,此时就没有可以利用的前缀信息,应该从零开始匹配了。若是把len设为next[len],这代表的是i的前一个字符和len指向的字符(next[len-1])相同,这是不对的
public int[] next(String pattern, int[] next){
next[0] = 0; //因为第一个字符没有前缀后缀,所以直接设置为0
int len = 0, i = 1;
while(i < m){
if(pattern.charAt(i) == pattern.charAt(len)){
len++; //匹配成功,最长前缀加1
next[i] = len; //当前i字符的最长前缀
i++; //该去匹配下一个字符了
}else{
if(len > 0)
len = next[len-1]; //去匹配它前一个字符
else{
next[i] = len; //此时len为0,代表与第一个字符都不同,所以最长前缀为零
i++; //匹配下一个字符
}
}
}
return next;
}
然后为了方便后面模式串与主串的匹配,可以将next数组整体右移一位,同时将next[0]设置为-1。
public int[] moveRight(int[] next){
for(int i = next.length-1; i > 0; -i)
next[i] = next[i-1]; //数组的数据右移
next[0] = -1;
return next;
}
最后就可以利用next数组进行模式串与主串的匹配了
public search(String mainStr, String pattern, int[] next){
int m = mainStr.length(), n = pattern.length();
int i = 0, j = 0;
while(i < m){
//匹配成功就去匹配下一个字符,j==-1表示没有可以利用的前缀后缀,只能从0开始匹配
if(j == -1 || mainStr.charAt(i) == pattern.charAt(j)){
i++;
j++;
}else{
j = next[j];
//前面next[j]都是匹配过的了,后缀前缀都一样,可以利用,直接匹配前缀的下一个字符
}
}
}