提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
KMP算法常用于解决字符串匹配问题,如字符串B在字符串A中是否出现过?出现过几次?出现的起始下标?
提示:以下是本篇文章正文内容,下面案例可供参考
一、字符串匹配的暴力算法
问题:字符串s为"aaaaa", t为"aaaabaaaaac",求s串在t串中出现的起始下标,若没出现则返回-1。
我们首先相当的肯定是暴力解法,假设t串匹配到i,s串匹配到j,则有:
(1)如果当前字符匹配成功(即t[i] == s[j]),则i++,j++,继续匹配下一个字符。
(2)如果当前字符匹配失败(即t[i] != s[j]),则令i = i -(j - 1),j = 0。相当于每次匹配失败i回溯到当前起点的下一个位置,j直接置为0。
(3)显然,用暴力算法会有大量的回溯,每次只移动一位,浪费了大量的时间。
代码如下(示例):
public int violence(String s, String t){
char[] chars1 = s.toCharArray();
char[] chars2 = t.toCharArray();
int i = 0;
int j = 0;
while (j < chars1.length && i < chars2.length){
if(chars1[j] == chars2[i]){
i++;
j++;
}else {
//当前起点的下一个位置,即起点移动一位
i = i - (j - 1);
j = 0;
}
if(j == chars1.length){//返回当前的起点
return i - j;
}
}
return -1;
}
二、KMP算法
1.部分匹配表
部分匹配值就是前缀和后缀的最长共有元素长度。部分匹配表详情我就不做介绍了。其实质就是有时候字符串头部和尾部会有重复如"ABCDAB"之中有两个"AB",那么它的部分匹配值就是2。
如在字符串"ABCDABCC"中找到字符串"ABCC",我们利用"ABCD"匹配"ABCC"时肯定匹配失败,按暴力解法我们会以A的下一个位置即B为起点继续匹配,而利用部分匹配表,ABCD部分匹配值为0,我们可以直接从ABCC中的A位置开始新一轮的匹配,节省了大量时间。
2.获取字符串的部分匹配表
获取字符串的部分匹配表即获得该字符串的所有子串的部分匹配值,例如字符串"ABCDABD"的部分匹配表为{0, 0, 0, 0, 1, 2, 0},因此可以编写一个方法返回一个next数组(即部分匹配表),数组长等于字符串长度,next[i]表示以i字符结尾的字符串的部分匹配值
代码如下(示例):
public int[] kmpNext(String dest){
int[] next = new int[dest.length()];
next[0] = 0;//字符串长度为1时部分匹配值为0
//变量i为遍历字符串的索引,变量j为当前字符串的部分匹配值
for (int i = 1, j = 0; i < dest.length(); i++){
while (j > 0 && dest.charAt(i) != dest.charAt(j)){
//理解这里为什么时j = next[j - 1]很重要
//如果j != 0说明next[i - 1]的位置的部分匹配值不为0
//此时我们求部分匹配值是在i - 1的基础上进行匹配的
//如果此时不符合 我们因该回溯到next[j-1] 再重新匹配
//这里不太好理解 因该结合具体实例来理解
//比如AABCDAAA next[] = {0, 1, 0, 0, 0, 1, 2, 2}
//当i = 7时 此时j = 2 charAt(7)与charAt(2)显然不等
//因为next[2 - 1] = 1 因此j = 1
//所以我们要回溯到charAt(7) 与charAt(1) 显然相等
//因此 next[7] = 2
j = next[j-1];
}
if (dest.charAt(i) == dest.charAt(j)){
j++;
}
next[i] = j;
}
return next;
}
3.KMP算法
KMP算法代码如下(示例):
public int kmp(String s, String t){
int[] next = kmpNext(s);
for (int i = 0, j = 0; i < t.length(); i++){
while (j > 0 && t.charAt(i) != s.charAt(j)){
j = next[j-1];//表示j处的字符不匹配 要回溯到next[j-1]处
}
if (t.charAt(i) == s.charAt(j)){
j++;
}
if (j == s.length()){
return i - j + 1;
}
}
return -1;
}
获取字符串s的部分匹配表即next数组,根据next数组,对字符串t和字符串s进行比较,找到s串在t串中的起始下标
总结
KMP算法常用于确定字符串t中是否包含字符串s、字符串t中字符串s的起始坐标、字符串t中包含几个字符串s等问题。
算法流程为:
(1)获取字符串s的部分匹配表
(2)根据字符串s的部分匹配表进行字符串s和字符串t的比较,根据题意获取答案
KMP算法的难点在于获取部分匹配表,理解j = next[j - 1]这处代码很关键。其实观察之后不难发现,获取部分匹配表的代码即kmpNext与kmp算法代码相似度非常高,KMP算法也是解决字符串问题的经典算法,应该熟练使用