1、什么是kmp算法?
kmp算法是一种字符串匹配算法,可以用于在一篇文章中查找关键字所在的位置,如果存在返回第一次出现关键位置的索引,如果不存在返回-1,kmp算法可以通过俗称看毛片算法记忆(拼音首字母组合)。
2、kmp算法的思想是什么?
kmp算法的主要思想是:在文本串(文章)中查找模式串(关键字)时,利用模式串和文本串中已经匹配的部分,跳过一些无用的比较,使文本串的标记指针不会回溯,模式串的标记指针移动。其实主要是模式串中如果有部分内容和文本串中一致,我们需要研究这些已知的匹配内容,这相当于我们需要研究模式串,发现模式串中存在的规律。
例如: 文本串:abcaabce
模式串:abce
上面的文本串和模式串第一次比较时,由于索引为3的位置a和e不相同,假设文本串的索引i=3,模式串的索引j=3,kmp的思想是由于前面的abc已经匹配完成,我们从中可以知道里面没有a,所以此时我们的文本串中标记指针不变依然是i=3,但是模式串的指针变化j=0,这样可以省略一些无用的比较,所以我们需要研究模式串发现其中的规律。
模式串中的规律主要有以下几个方面,假设模式串如图:
1. 为什么可以跳过模式串的一些不必要的比较:
当P[j]!=T[i];
已知P[0 ~ j-1]=T[i-j ~ i-1];
则:P[j-k ~ j-1] = T[i-k ~ i-1] ;
当存在:p[0 ~ k-1] =p[j-k ~ j-1];
则:P[0 ~k-1] = T[i-k ~ i-1] ;
所以可以跳过模式串的前面k-1个数值的比较,节省时间。
2. 图中的k是如何得到的?
注意上面的公式要求:p[0 ~ k-1] =p[j-k ~ j-1],这里就存在两个概念前缀和后缀,前缀指的是除了最后一个元素,前面元素的顺序组合,同理理解后缀,比如abab的前缀是{a, ab,aba},而abab的后缀是{b,ab,bab},其前缀中和后缀中相同部分的最大值是2,如果存在索引则:p[0 ~ 1] = p[2 ~ 3];所以,如果我们想跳过一些无用的比较,我们首先需要确定的是模式串中不同索引位置的k值,通过上面图可知如果跳过索引则需要比较的是文本串索引为7的b和模式串中索引为2的c,所以当p[6]!=T[7],文本串的i不变,j移动到索引为2的位置,k=2,这是abcdab模式串前缀和后缀的最大值,求模式串中某索引的k值是除当前索引元素的前面元素前缀和后缀的最大值,我们使用next数组进行存放。
3. next[0]为什么是-1,不是0?
next[0]是指当模式串为abc,文本串为efg,当a!=e,则模式串的索引调到索引为next[0]处,如果next[0]会造成循环,所以next[0]!=0,另外,next[1]=0;因为索引为1的元素其前面只有一个元素,所以k=0;
4. 推导next[j]=k,当p[j]=p[k],next[j+1]=k+1;
next[j]=k,则p[0 ~ k-1] =p[j-k ~ j-1];,由于p[j]=p[k],则p[0 ~ k] =p[j ~ j-1], 所以next[j+1]=k+1=next[j]+1
5. 推导next[j]=k,当p[j]!=p[k],则k=next[k];(重点)
next[j]=k,则p[0 ~ k-1] =p[j-k ~ j-1],由于p[j]!=p[k],所以我们需要求p[0 ~ j]中前缀和后缀相同的最大值,假设存在最大值是b,则p[0 ~b -1] =p[j-b+1 ~ j],则next[j + 1]= b,如果b大于k,这可以保证p[j]=p[k],但是p[j]!=p[k],所以b要小于k,则b在0 ~ k中间的数,所以在p[0 ~ k-1]中存在p[j]的前缀和后缀的最大值,将0 ~ k-1分为两部分,k-1和0 ~ k ~ 2,k-1和索引j比较,0 ~ k ~ 2和j-k+1 ~ j-1,由于p[0 ~ k-1] =p[j-k ~ j-1], j-k+1 ~ j-1等于1 ~k -1 ,则求解的是next[k] ,则k=next[k];如果p[j]=p[k],则k++,否则k=next[next[k]]
3、kmp算法的代码实现?
public static void setNext(char[] target, int[] arr) {
arr[0] = -1;
int k = -1;
int i = 0;
while (i < target.length - 1) {
if (k == -1 || target[i] == target[k]) {
++k;
++i;
arr[i] = k;
} else {
k = arr[k];
}
}
}
public static int getIndex(char[] text, char[] target) {
int[] arr = new int[target.length];
setNext(target, arr);
for (int i : arr) {
System.out.println(i);
}
int l = 0;
int k = 0;
while (l < text.length) {
if (k == -1 || text[l] == target[k]) {
++l;
++k;
} else {
k = arr[k];
}
if (k == target.length) {
return l - k;
}
}
return -1;
}
4、kmp算法的优化
kmp算法的优化:kmp算法中存在的问题是:当T[i]!=p[j],但是p[next[j]]=p[j],由于T[i]!=p[j],所以p[next[j]]!=T[i],有重复判断,所以当p[j]=p[next[j]]需要改变,使next[j]=next[next[j]];代码如下:
private static void getNext(String target, int[] next) {
char[] chars = target.toCharArray();
next[0]=-1;
int k = -1;
int j = 0;
while (j < chars.length-1) {
// 比较前缀和后缀或者k=-1
if (k == -1 || chars[j]==chars[k] ) {
// 如果前面成立则next[j]=k;则下面比较的是chars[j] == chars[next[j]]
if (chars[++j] == chars[++k]) {
// k=next[j] 下面为next[j]=next[k]=next[next[j]]
next[j]=next[k];
} else {
next[j]=k;
}
} else {
k = next[k];
}
}
}
5、kmp算法的总结
kmp算法用于快速的字符串匹配,先从模式串中获取足够的规律,再快速再文本串中匹配,文本串中的索引不回溯,kmp算法的重点是获取next数组,如果要获取next数组,需要明白模式串中前缀和后缀的规律(p[0 ~ k-1] =p[j-k ~ j-1])。假如文本串的长度为m,模式串的长度为n,则获取next数组花费时间为O(n),文本串中比较的时间为O(m),总的时间为O(n+m)
6、kmp算法类比
kmp算法类思想似于:如果我们想快速的提升自己某个方面的知识,我们首先必须自己和自己比较,了解自己的相关知识体系,然后才能快速的提升,是一种先了解自己,才可以快速的了解他人的思想