自学记录所用,不对之处望请斧正
引入:KMP算法是为了实现index函数(子字符串在主字符串中的首次出现的位置)如下图所示
若规定字符串索引从0开始,则子字符串在主字符串中的首次出现的位置为2;
index函数实现:
算法一:暴力算法
从主串起始位置依次向后截取与子串长度相同的字符串,在与子串进行比较。代码如下:
int index(String s,String t){//s为主串 t为子串
if (s.length() < t.length())return -1;
int i = 0;
while (i < s.length() - t.length() ){
String temp = s.substring(i,i + t.length());
if (t.equals(temp))return i;
i++;
}
return -1;
}
算法二:滑动窗口算法(朴素匹配)
将子串首个字符与主串的字符分别比较,若不同则比较下一个,若相同则将主串指针和子串指针分别后移依次比较是否相同。代码如下:
int index(String s,String t){
if (s.length() < t.length())return -1;
int i = 0;
int x = 0;
while (i < s.length() - t.length() ){
if (s.charAt(i) == t.charAt(x)){
int k = i + 1;
int j = 1;
while (j < t.length() && s.charAt(k) == t.charAt(j)){
k++;
j++;
}
if (j == t.length())return i;
}
i++;
}
return -1;
}
或者这样也可以:
int index(String s,String t){
if (s.length() < t.length())return -1;
int i = 0;//主串指针
int x = 0;//子串指针
int k = 0; //记录主串位置
while (i < s.length() && x < t.length()){
if (s.charAt(i) == t.charAt(x)){
i++;
x++;
}else {
k++;
i = k;
x = 0;
}
}
if (x == t.length())return k;
return -1;
}
两个代码实现原理一样。
算法三:KMP算法
为了减少朴素算法(算法二)的比较次数,KMP出现了!
如上图,当子串与主串比较到箭头所指位置时,主串与子串对应字符不相同,此时观察子串箭头位置左部分有AB这一组相同的前后缀,不难发现我们只需要移动子串位置使前缀到达移动前的后缀位置即可在正确的前提下减少比较次数。如下图所示:
在主观视觉上我们觉得子串向右移动而实际上是箭头(子串指针)向左移动到前缀的后一个位置,而且我们可以直接获得子串的每一个位置的前后缀相同时的长度,有意思的地方来了,我们甚至可以直接声明一个数组用来存放子串在该位置与主串不相同时应该左移到子串的哪个位置!我们的next数组来了!
如何获取next数组的每一个元素呢!(假设字符串从1开始)
手算:计算前后缀相同时的长度 + 1;next[ i ]最多比next[ i - 1 ]大1;
算法获取:代码如下:
void getNext(char c[],int[] next){
next[1] = 0;
int i = 1;
int j = 0; //j为了记录next[i]的状态
while (i < c.length){
if (j == 0 || c[i] == c[j])next[++i] = ++j;
else j = next[j];
}
}
然后就可以改进算法了得到KMP算法了
int index(String s,String t){
if (s.length() < t.length())return -1;
int i = 0;//主串指针
int x = 0;//子串指针
int k = 0; //记录主串位置
int[] next = new int[t.length + 1];
getNext(s.toCharArray(),next);
while (i < s.length() && x < t.length()){
if (s.charAt(i) == t.charAt(x)){
i++;
x++;
}else {
x = next[x];
}
}
if (x == t.length())return k;
return -1;
}
KMP优化:KMP的优化就是next的优化,即在赋值next的各个值时查看前面有没有相同字符若有则直接用其数值。获得nextval