一、概述
什么是KMP算法?KMP算法是一种用于在字符串A中找到目标字符串B位置的算法。这个算法是由Donald Knuth、James H. Morris、Vaughan Pratt三人于1977年联合发表,故称为“KMP算法”。简而言之KMP就是一种字符串查找法。本章就来带大家一起了解下KMP算法的实现逻辑。
二、问题分析
光看上面的定义可能大家并不知道KMP到底是个什么东西?到底怎么工作的?接下来我们通过一个问题来帮助大家很好的理解KMP算法。
问题:在A字符串"AC12BS 1BC12UD12BSCBA12BSCBA12CBSA12"中找出指定B字符串“12BSCBA12CB”出现的位置。
首先用暴力匹配法实现的思路是:取出B字符串的第一个字符,其位置记做【i】,遍历A字符串的每个字符,位置记做【j】。将 A[j] 和 B[i] 比较,相同则 i++,j++ 继续匹配。否则 j 回溯到 j-i 的位置,i 置为0,继续匹配,直到得出结果为止。下面附上暴力匹配代码
public int violenceSearch(String str, String target) {
// 遍历str字符串
for (int i = 0, j = 0; i < str.length(); i++) {
// 给定找到结果的条件
while (j == target.length() - 1)
return i - j;
if (str.charAt(i) == target.charAt(j))
j++;
else {
// 未匹配到,将i进行回溯
i = i - j;
j = 0;
}
}
return -1;
}
如上代码就是暴力匹配的具体实现,我们可以从代码中看出在匹配失败的时候有一个回溯重新匹配的过程。无疑这样是很浪费时间的。怎样才能优化呢?优化方式就是我们所讲的KMP算法,接下来我们就来分析下KMP算法的实现思路。
三、KMP算法实现思路
首先我们先来看看,如果将要匹配的字符串B变为"A12CB",我们再用以上暴力匹配整改的代码执行一遍看看结果
public int violenceSearch(String str, String target) {
// 遍历str字符串
for (int i = 0, j = 0; i < str.length(); i++) {
// 给定找到结果的条件
while (j == target.length() - 1)
return i - j;
if (str.charAt(i) == target.charAt(j))
j++;
else {
// 未匹配到,将i进行回溯
// i = i - j;
j = 0;
}
}
return -1;
}
以上代码在暴力匹配的基础上注释了回溯的步骤,执行一下代码,结果依然正确,而如果匹配"12BSCBA12CB",则执行的结果并不正确。那么很容易想到:是否要回溯匹配或者回溯多少位匹配,依赖于目标字符串。这便是KMP算法优化的核心思想。那么查找的字符串具有怎样的特点,该怎样定位回溯的位置呢?
我们引入一个概念叫做,子串前后缀,如图便是展示目标字符串所有子串的前缀和后缀,以及前后缀公共元素的最大长度(以下简称:最大长度)。
KMP算法的核心:目标字符串失配时,整个目标字符串向右移动的位数=失配字符上一个字符对应的最大长度。
知道这个结论以后,我们往下看匹配图解
开始匹配,如下图
继续遍历,直到目标字符串第一位匹配上,如下图,但是此时匹配到第5位时,未匹配上,失配字符【C】的上一位【S】的最大长度可从长度表中查出,结果为0,即目标字符串不需要向右移动
继续匹配,如下图,第2个字符失配,我们查下长度表,发现上一位对应的最大长度为0,即目标串不需要右移
继续如下图,同上,目标串不需要右移
如下图,匹配到倒数第二个字符【C】时失配,我们查找该字符的上一个字符对应的最大长度,发现为2,即目标串要向右移动两个单位
如下图,最终匹配上
四、代码实现
经过上面的思路分析,我们知道KMP算法的核心就是要找出目标字符串的最大长度表,下面我们来进行代码实
public int[] kmpMaxLengthTable(String str) {
int[] table = new int[str.length()];
table[0] = 0; // 长度表的第一个值为0
for (int i = 1, j = 0; i < str.length(); i++) {
// 当子串不相同时,回溯获取最大长度
while (j > 0 && str.charAt(i) != str.charAt(j))
j = table[j - 1];
// 当子串相同时,长度加1
if (str.charAt(i) == str.charAt(j))
j++;
table[i] = j;
}
return table;
}
上面的代码便是整个KMP算法的核心,查找子串的最大长度表,实现的思路有点难度,需要仔细体会一下。
最后,我们利用算出的最大长度表优化我们初步实现的暴力匹配法,下面给出代码
public int kmpSearch(String str, String target) {
// 获取子串最大长度表
int[] table = kmpMaxLengthTable(target);
// 遍历str字符串
for (int i = 0, j = 0; i < str.length(); i++) {
// 给定找到结果的条件
while (j == target.length() - 1)
return i - j;
if (str.charAt(i) == target.charAt(j))
j++;
else {
// 未匹配到,则i就要进行回溯,回溯值根据最大匹配表来
i = i - table[j];
j = 0;
}
}
return -1;
}
至此,我们已经完成了KMP算法的实现。
五、小结
本章主要介绍了KMP算法,KMP算法就是字符串匹配法。我先用暴力匹配法解决文中抛出的问题,然后分析该方法的性能,及其时间复杂度,其复杂度为O(M*N),再分析该方法是否有优化空间,引出KMP算法,最后用代码实现。(KMP算法的时间复杂度为O(M+N))
面试和工作中会经常遇到字符串相关的问题,希望各位可以灵活运用KMP算法。
感兴趣的读者朋友可以 关注本公众号,和我们一起学习探究。
本人因所学有限,如有错误之处,望请各位指正!