KMP算法是一种无回溯算法,由Knuth、Morris、Pratt等人发现,用以改进传统的字符串的模式匹配算法
思想:当在某个位置匹配不成功的时候可以根据之前的匹配结果从模式字符串的另一个位置开始,而不必从头开始匹配字符串
那么下次匹配的位置如何确定就成了问题关键
为了更好地阐述,我们引入几个概念
- 前缀子串:模式串P开头的前k个字符, p0, p1 …,pk-1;
- i位置的后缀子串:在P第i位置的左边,取出k个字符,即pi-k+1,…,pi;
- 第i位的最长前缀串 :与i位置后缀子串相等的最长前缀子串。
-
特征数n[i]:第i位的最长前缀串的长度k就是模式串P在位置i上的特征数n[i];
-
特征向量:特征数组成的向量称为该模式串的特征向量
一旦匹配过程中pi与tj比较不等,可用p中以n [i-1]为下标的字符与tj进行比较
特征数定义:
举个例子,P=cococola
(如果浏览过万我就找可口可乐拉赞助去hhhhh)
特征数计算:
计算流程
匹配流程
还是以上文的字符串为例
算法复杂度
KMP匹配算法的时间复杂度O(m+n)。
计算特征数的时间复杂度为O(m)
计算时间复杂度时采用了均摊分析的方法,在此很难展开说明,有兴趣可以自行了解
Java代码实现
/**
*
* 在目标串中查找模式串首次出现的位置
* @param haystack 目标串
* @param needle 模式串
* @return 0 模式串为空
* -1 无法匹配
* int 首次出现的第一个元素下标
*/
public static int kmp(String haystack, String needle) {
if (needle.equals("")) return 0;
if (needle.length() > haystack.length()) return -1;
int[] next = kmpnext(needle);
for (int i = 0, j = 0; i < haystack.length(); i++) {
while (j > 0 && haystack.charAt(i) != needle.charAt(j)) {
j = next[j - 1];
}
if (haystack.charAt(i) == needle.charAt(j)) {
j++;
}
if (j == needle.length()) {
return i - j + 1;
}
}
return -1;
}
/**
* 得到模式串的特征向量
* @param needle 模式串
* @return int[] 特征向量
*/
public static int[] kmpnext(String needle){
int[] next = new int[needle.length()];
next[0] = 0;
for(int i = 1,j = 0; i < needle.length(); i++){
while(j > 0 && needle.charAt(j) != needle.charAt(i)){
j = next[j - 1];
}
if(needle.charAt(i) == needle.charAt(j)){
j++;
}
next[i] = j;
}
return next;
}