目录
有一个字符串 str1 = "BCDABCDABABCDABCABCD",和另一个字符串 str2 = "ABCAB"。
现在要判断 str1 是否含有 str2,如果存在,就返回第一次出现的位置,如果没有,则返回-1。
KMP算法:
一、介绍
Knuth-Morris-Pratt字符串查找算法,简称为“KMP算法”,常用于在一个文本串S内查找一个模式串P的出现位置,这个算法由Donald Knuth、Vaughan Pratt James H.Morris三人于1977年联合发表,故取这3人的姓氏命名此算法。
二、作用与使用
(1)KMP是一个解决模式串在文本串是否出现过,如果出现过,最早出现的位置的 经典算法 。
(3)KMP方法算法就利用之前判断过信息,通过一个next数组,保存模式串中前后最长公共子序列的长度,每次回溯时,通过next数组找到,前面匹配过的位置,省去了大量的计算时间。
三、图解
1、首先,用 str1 的第一个字符和 str2 第一个字符去比较,同暴力匹配算法一样,不符合,关键词向后移动一位。
2、重复第一步,若还是不符合,则继续向后移动
3、一直重复,直到 str1 有一个字符与 str2 的第一个字符相同为止。
4、一直重复,直到 str1 有一个字符与 str2 对应的字符不符合。
5、若此时使用暴力匹配算法,指针回溯到一开始发现字符相同时的下一个字符,重复第一步,这其实是很不明智的。用暴力方法解决的话就会有大量的回溯,每次只移动一位,若是不匹配,移动到下一位接着判断,会浪费了大量的时间。
实际上,我们在进行匹配时,其实已经知道了 str2 前面四个字符为"ABCA"。KMP算法的想法是,设法利用这个已知信息,不要把”搜索位置”移回已经比较过的位置,继续把它向后移,这样就提高了效率。
6、我们对 str2 计算出一张“部分匹配表”。
7、返回到第四步,已知D与A不匹配,而前面三个字符“ABC”是匹配的。查表可知,最后一个匹配字符C对应的部分匹配值为0。那么可以按照以下公式来计算需要向后移动的位数:
移动位数 = 已匹配的字符数 - 对应的部分匹配值
因为 4 - 0 等于 0,所以指针从一开始发现字符相同时的位置向后移动四位。
8、又因为A与C不匹配,此时已匹配的字符数为2(“AB”),对应的部分匹配值为0,所以移动位数为 2 - 0 = 2,指针后移两位。此后一直重复该步骤直到达成目的或结束。
四、代码演示
public class KMPAlgorithm {
public static void main(String[] args) {
String str1 = "BCDABCDABABCDABCABCD";
String str2 = "ABCAB";
int[] next = kmpNext(str2);
System.out.println(Arrays.toString(next));
System.out.println(kmpSearch(str1,str2,next));
}
/**
* kmp搜索算法
* @param str1 源字符串
* @param str2 字串
* @param next 子串对应的部分匹配表
* @return 如果是-1就是没匹配到,否则返回第一个匹配的位置
*/
public static int kmpSearch(String str1, String str2, int[] next){
//遍历
for (int i = 0, j = 0; i < str1.length(); i++){
//需要处理str1.charAt(i) != str2.charAt(j),调整j的大小
//kmp算法核心点
while (j > 0 && str1.charAt(i) != str2.charAt(j)){
j = next[j-1];//*************
}
if(str1.charAt(i) == str2.charAt(j)){
j++;
}
if(j == str2.length()){//找到了
return i - j + 1;
}
}
return -1;
}
//获取到一个字符串(子串)的部分匹配值表
public static int[] kmpNext(String dest){
//创建一个next数组保存部分匹配值
int[] next = new int[dest.length()];
next[0] = 0;//如果字符串的长度为1,那么不管它是什么,它的部分匹配值都是0
for (int i = 1, j = 0; i < dest.length(); i++){
//当dest.charAt(i) != dest.charAt(j)满足时,我们需要从next[j-1]获取新的j
//直到我们发现有dest.charAt(i) == dest.charAt(j)成立时才退出
//这是kmp算法的核心点
while (j > 0 && dest.charAt(i) != dest.charAt(j)){
j = next[j-1];//*************
}
//当dest.charAt(i) == dest.charAt(j)满足时,部分匹配值就+1
if(dest.charAt(i) == dest.charAt(j)){
j++;
}
next[i] = j;
}
return next;
}
}