一般来说,如果让我查看字符串A是否包含字符串B,直接循环对比也就了事,不过就是计算量会大很多,可能有重复对比的可能。如strA= “ABCABDABCABC” ,strB = “ABCABC”,当你对比第一个6位字符串不相等的时候,第二次开始对比,或者说继续对比,再次拿strB中的‘A’和strA中的‘B’进行对比么?这样如果两个字符串都很长,比如到了上百甚至上千,你已经对比了99个字符串,发现不一致,又要重新对比,是不是有点心累的感觉,或者说有那么点不爽?
KMP就是针对这个问题的,让你可以不要进行重复匹配,不过理解起来么,有点小头疼。
简单说下个人理解,恩,仅供参考,如有差别,请以相关技术文档为主,【嘿嘿】。
使用KMP进行字符串对比,分为两步,第一个就是对子串进行分析,看一看子串中是否有和自己开始部分重合的部分,如果没有重合,比如strC = “ABCDEFG”,比如对比到’D'不一样了,那别说了,重新对比,原字符串,也不用回退,直接让子字符串从当前开始对比就行了。如果strD =“ABCABD”;当对比到D的时候不一样了,相当于已经对比了AB,下次对比,是不是直接对比D也就行了?也就是C后面的AB已经对比过了,不用重新对比,如果说原字符串有个循环下标i的话,相当于i一直在增加,不会有双层的重复循环。
当然,如果i一只增加,又能准确的找到需要与子串对比的那个字符,肯定是要对子串进行处理了。其实就是检查是否与开始子串是否有重复的。它会维护一个数组,标记是要和第几个字符串进行对比。
如:“ABCDEF”,那么对应的数组就是[0,0,0,0,0,0]。也就是每个字符 都对应一个 下次和原字符串对比最小位置,比如说,对比到E,发现原字符串是H,那么这个字符串也就要重新和第0位对比也就是A,如果原字符串也是E,那就继续向下走。
再比“ABDABDABE”,子串是“ABDABE”,子串对应的数组是[0, 0, 0, 1, 2, 0],子串对比到第六个字符,也就是E的时候,发现D和E不匹配,原字符串当前字符串是D,并且刚刚对比完的2个字符AB,刚好子串的开始字符AB相同,是已经对比过的。我现在直接去对比D(子串对应的下标为2),不就好了么?然后继续向下走,是不是?
看懂的话,咱们继续,还没明白的话,恩,就多看看,嘿嘿。
那么怎么获取子串对应的数组或者说是匹配值表呢?大家观察一下,其实也就是对比呗,也就是上一个字符的对应位置的匹配值就是当前字符需要对比的下标位置的字符。
比如说上面的0,也就是要对比子串的第一个字符,2也就是要对比第三个字符。
那么,怎么获取这个匹配值数组呢?大家可以观察下,其实这个数组也就是从首字母开始对比查看的,从当前字符开始,如果和首字母相同,也就是说,下次和原字符串对比,下一个字符只需要对比 原字符串的下一个字符就好了;如果当前字符下一个字符和开始字符的下一个字符也相同,那么,下下一个字符只需要对比 原字符串的第三个字符也就可以了。以此类推,匹配值其实也就是一个子串内部对比的过程,查询子串中是否有和子串的开始部位相同的字符串段,类似于 残缺子串的循环查询 吧。
下面看下代码吧。匹配值表查询:
//匹配值表
static private int[] getNext(String str){
int[] next = new int[str.length()];
next[0] = 0;
// 使用下一位和之前对比过的字符串进行对比,发现有从起始串相同的,记录位置,后面对比的时候根据这个进行回退,或者说
// 判断下一个可能相同的字符串当前字符位于第几个
for (int i = 1,j = 0; i < str.length(); i++) {
// 如果不相等,说明当前字符缘分已到,到了这里,和开始的那几个字符不一样了,于是while循环
// 看一看当前字符 和 前面几个字符能不能组成一个短一点的 和 开始的那几个字符串一样的 字符串段
// 比如 AABAAAB ([0, 1, 0, 1, 2, 2, 3]),对比到第六位的时候,AAA和开始的AAB不同,于是,第六个字符A委屈求全,
// 退一位,对比第二个A,总行了吧?咦,刚刚好对得上
while (j > 0 && str.charAt(j) != str.charAt(i)){
j = next[j-1];
}
//如果相等,那就下一位呗,字符串循环反正不会停下
if (str.charAt(i) == str.charAt(j)){
j++;
}
// 不管相等不相等,匹配值总是要给滴~~
next[i] =j;
}
return next;
}
有了这个,对比的代码应该就可以搞定了,我这边也就不啰嗦了,直接上代码!
public static void main(String[] args) {
String str = "ABDABDABE";
String temp = "ABDABE";
int[] next = getNext(temp);
// 需要对比的子串的位置
int j = 0 ;
for (int i = 0; i < str.length(); i++) {
// 字符不相等
// 递归向前查找子串中和当前字符相等的字符呗,找到了,就从那一位的匹配值开始
//至于找不到,呵呵,大不了重头再来,回归原始的零蛋,【你懂的】
if (j > 0 && str.charAt(i) != temp.charAt(j)) {
j = next[j - 1];
}
// 相同的话,子串下次对比下一位
if (str.charAt(i) == temp.charAt(j)) {
j++;
}
// 子串对比完了,那不就是找到了,搞定!!
if (j == temp.length()) {
System.out.println(i - j + 1);
break;
}
}
if (j != temp.length()) {
System.out.println("没有找到匹配值!!");
}
}
恩,大致逻辑也就是这样了,需要注意的是,原字符串遍历的时候从前往后,子串遍历的时候,如果不等,要从后往前,因为前面的已经对比过,找的是,最近的没有对比成功的字符。
大致也就是这个样子了,大家如果对KMP算法有更深一层的兴趣,可以去看一看算法的详解,推荐一个知乎关于KMP算法的地址 ,我这里就不多啰嗦了。
谢谢观看,感觉还行的话,点个赞呗!
no sacrifice,no victory~