KMP算法在字符匹配的题目中尤为重要,因其效率高而备受好评,可谓是算法人必学算法之一。
要了解KMP算法,首先我们可以先来了解一下BF算法。
题目:判断在字符串中abcababcabc(原字符串)中子字符串abcabc(目标字符串)的位置。
在具体了解KMP算法之前,我们先来回顾一下BF算法:
一、BF算法
第一次碰到这种题目,在未了解KMP算法之前,第一想法应该是一个一个字母和目标字符串进行匹配。
tmp一开始和i一样,指向原数组下标为0的位置
当j和tmp指向的元素相同时,j和tmp向后遍历,知道j走完整个目标数组
当j和tmp指向的元素不相同时,说明i位置并不是目标数组在元素组中的匹配位置,则i前进一位,同时j复位到0,知道j能够走完整个目标数组,或者是i走完了整个数组。
这种算法很好理解,但是为什么还要学习KMP算法呢?
那是因为BF算法存在了很多重复的步骤,我们可能脑子里有个大概的感觉,觉得BF算法确实比较繁杂,但是具体重复在哪里呢?
二、KMP算法
原字符串:abcababcabc 目标字符串:abcabc
如下情况,在BF算法中,只能是目标字符串后移动,与原字符串第二个字母再进行比较:
a | b | c | a | b | a | b | c | a | b | c |
a | b | c | a | b | c |
而此时我们会想,前面的字符已经匹配过了,能不能找出不匹配字符的前字符串有什么特点从而不让i(tmp)进行前移呢?
那不让i(tmp)进行前移的话,那j势必就要进行回退,怎么回退呢?
仔细观察:
a | b | c | a | b | a | b | c | a | b | c |
a | b | c | a | b | c(j) |
我们要让j进行回退并且能够匹配上的话,就说明 原字符串a 前的那段字符串(就是那段已经匹配好的字符串的尾巴要被作为新的能匹配上的字符串的头),而不匹配字符之前的字符都是与目标字符串的字符进行匹配的,所以我们只需要找在目标字符串中,不匹配字符之前的字符串的头尾相同的部分的长度,并记录下来,然后j直接跳到长度位置。
a | b(头) | c | a | b(尾) | a | b | c | a | b | c |
a | b(头) | c(j) | a | b(尾) | c |
因为每一个字符都有可能作为不匹配字符,所以我们需要一个和目标字符串长度一样的next数组来记录每一个字符其之前字符串的头尾相同的部分的长度。
Next数组(首字符下是-1,因为首字符都不匹配的话,那目标字符串只能与原字符串的下一个字符进行匹配了):
a | b | c | a | b | c |
-1 | 0 | 0 | 0 | 0 | 2 |
若j指向字符一直不匹配则j不断回退(思路和前面一样),知道j指向的字符和i指向的字符匹配,i和j一起++,或者j等于了-1,则j=0,i++;
1、代码编写Next数组
人脑是很容易也很快可以找出前字符串头尾相同的字符串长度是多少的,可计算机怎么知道呢?我们可以使用dp的思想,
(x代表未知,上面代表的是目标字符串,下面代表的是Next数组)
x | x | a(tmp) | x | x | x | a(j-1) | x | x | x |
x | x | x | x | x | x | 2 | ?(j) |
我们现在要填写问好处的next值,现在我们知道tmp=next[j-1]=2,(原字符串用str表示),若str[j-1]==str[tmp],则next[j]=tmp+1
那如果不相等呢?即头尾匹配的字符长度肯定要减少:
x | x | x | a(tmp) | x | x | x | x | b(j-1) | x |
x | x | x | 1 | x | x | x | x | 3 | ?(j) |
x | x | x | a(tmp) | x | x | x | x | b(j-1) | x |
x(*) | x | x | 1 | x | x | x | x(*) | 3 | ?(j) |
则tmp=next[tmp],直到相等或者tmp=-1,next[j]=tmp+1
2、代码
private int[] nextCreate(String dest){
int[] next=new int[dest.length()];
next[0]=-1;
for (int i = 1; i < next.length; i++) {
int tmp=next[i-1];
while(tmp!=-1&&dest.charAt(i-1)!=dest.charAt(tmp)){
tmp=next[tmp];
}
next[i]=tmp+1;
}
return next;
}
public int findIndex(String scr,String dest){
int i=0;
int j=0;
int[] next=nextCreate(dest);
while(i<scr.length()){
while(i<scr.length()&&j<dest.length()&&scr.charAt(i)==dest.charAt(j)){
i++;
j++;
}
if(j==dest.length()){
return i-dest.length();
}
j=next[j];
if(j==-1){
j=0;
i++;
}
}
return -1;
}
可以做做题目练手:
leetcode28—找出字符串中第一个匹配项的下标