问题:给一个字符串str1,再给一个字符串str2,看能不能再str1中匹配出str2,即看在str1中是否包含str2
本文以str1=“BBCABCDABABCDABCDABDE” str2=”ABCDABD”
为例,进行思路分析。
kmp算法中最重要的就是算出str2的部分匹配表。
要算出部分匹配表,先了解两个概念。
最优前缀:一个字符串中,去掉一个或多个尾部的字符所得到的新的字符串就是该字符串的最优前缀。例:abc的最优前缀有:a,ab。
最优后缀:一个字符串中,去掉一个或多个首部的字符所得到的新的字符串就是该字符串的最优后缀。例:abc的最优后缀有:c,bc。
部分匹配表中数字的含义为:既是最优前缀,又是最优后缀的最长字符串的长度。
现在计算str2的部分匹配表:
str2=”ABCDABD”
(1)长度为1的字符串是不存在什么最优前缀与最优后缀的。
(2)AB的最优前缀为A,最优后缀为B,所以部分匹配表中相应的值为0。
(3)ABC的最优前缀分别为A,AB,最优后缀为C,BC,所以部分匹配表中相应的值为0。
(4)ABCD的最优前缀为A,AB,ABC,最优后缀为D,CD,BCD,所以部分匹配表中相应的值为0。
(5)ABCDA的最优前缀为A,AB,ABC,ABCD,最优后缀为A,DA,CDA,BCDA,所以部分匹配表中相应的值为1,因为既是最优前缀又是最优后缀的为A。
(6)ABCDAB既是最优前缀又是最优后缀的为AB,所以相应值为2。
(7)ABCDABD既是最优前缀又是最优后缀的为长度为0。
所以ABCDABD的部分匹配表为:table[]=0000120。
str1=“BBCABCDABABCDABCDABDE” str2=”ABCDABD”
在字符串str1中匹配了str2中前6个字符。
我们在字符匹配表table中查到前六个字符所对应的,既是最长前缀又是最长后缀的最长长度为2,既是table[5]中的值。
后移位数n=6-table[5]。
为什么后移n这么算?
现在str1与str2中已经匹配了ABCDAB六位字符。匹配不下去了,要找到下一处继续进行匹配,因为ABCDAB的最长前缀和最长后缀的重叠最长为2,那么就是移动到最后两位重新开始匹配,即向后移动位数n=6-table[5].
以上就是整个KMP的算法思想。
代码实现:
public int strStr(String haystack, String needle) {
int len1=haystack.length();
int len2=needle.length();
if(len2>len1)
return -1;
if(len2==len1 && haystack.equals(needle))
return 0;
if(len2==len1 && !haystack.equals(needle))
return -1;
if(len2==0)
return 0;
int[] table=new int[len2]; //KMP算法的table数组
for(int k=1;k<len2;k++){ //计算KMP算法的table,k表示正在计算needle的第几个字母的table值
for(int i=k;i>0;i--){ //检查最优前缀和最优后缀的长度,先检查长度为k的前缀和后缀,后检查长度为k-1的前缀和后缀
for(int j=0;j<i;j++){ //j表示针对长度为i的前缀和后缀,现在比较了几个字母
if(needle.charAt(k-j)!=needle.charAt(i-j-1)){ //发现在某个字母不一样了,表示长度为i的前缀和后缀不能匹配
break;
}
if(j==i-1){
table[k]=i;
i=0;
}
}
}
}
int i=0;
int j=0;
while(j<len2){
if(j==len2-1 && haystack.charAt(j+i)==needle.charAt(j))
return i;
if(haystack.charAt(j+i)!=needle.charAt(j)){
if(j==0){
i=i+1;
j=-1; //因为后面会进行j++,所以设为-1
}
else{
i=i+j-table[j-1];
j=table[j-1]-1;
}
if(i>len1-len2)
return -1;
}
j++;
}
return -1;
}