应用场景
在淘宝这个处理数据的平台上经常进行字符串的匹配,查找,替换等操作,尤其对搞搜索的同学来说非常的重要。KMP算法由Knuth、Morris、Pratt 同时提出来的,取了这三个人的名字的头一个字母,所以叫做KMP算法。
原理分析
KMP算法主要利用模式串(要匹配的子字符串)自身的对称特点来节省比较的次数。先看看一般的字符串匹配算法。主串A=abcdabcdefg,子串(模式串)B=abcde。
第一步A=abcdabcdefg 从首字母开始比较,一直比较到A[4]!=B[4] 比较失败。
B=abcde
第二步A=abcdabcdefg A串从A[1]开始,B串从B[0]开始 比较 一直到A[1]!=B[0]。
B=abcde
第三步A=abcdabcdefg A串从A[2]开始,B串从B[0]开始 比较 一直到A[2]!=B[0]。
B=abcde
一直循环下去直到在A串中找到与B串一样的子串,这种算法的时间复杂度是O(mn)。
再看看KMP算法:
KMP算法需要两个数组游标指针A串的A[i]游标指针i,B串的B[j]游标指针j。
主串A=ababadef ,子串B=ababag
i 0 1 2 3 4 5 6 7 ……
A a b a b a b e f ……
B a b a b a g
j 0 1 2 3 4 5
第一步 i和j 同时自增1,比较A[i]和B[j]。直到i==5,j==5时 A[i]!=B[j]。
i 0 1 2 3 4 5 6 7 ……
A a b a b a b e f ……
B a b a b a g
j 0 1 2 3 4 5
一般的算法在遇到A[i]!=B[j]时 i回溯到i==1,j回溯到j==0然后再比较,但KMP算法的i 不回溯到i==1,而是让j 改变到j1 ,再进行第二步A[i]与B[j1]的比较。
第二步让j 改变到j1 ,进行A[i]与B[j1]的比较。j1 ==4 。这时A[i]==B[j1],为什么j 变成j1==4 对于理解KMP算法非常重要。因为子串B的子串{B[0]….B[4]} 有对称性,B[0] B[1] B[2]== B[2] B[3] B[4]。j1 就是要在B串的头j1个字符中找到这样对称的字符串,当然j1越大越好。 正是利用这样的对称性 就相当于从A[2]与B[0]开始比较,又因为A[2] A[3] A[4]== B[0] B[1] B[2] 所以不需要将i==5回溯到i==1直接进行符A[5]与B[3]的比较。就这样比一般算法节省了i回溯到i==2,i==3,i==4的比较的那些次数。要想节省这些比较次数就的在B串中找到尽可能长的从B[0]开始对称串,就是找到尽可能大的j1 使得{B[0]……B[j1-1]}=={B[j1-1]……B[j-1]}。
i 0 1 2 3 4 5 6 7 ……
A a b a b a b e f ……
B a b a b a g
j 0 1 2 3 4 5
第三步当比较到A[6]!=B[4]时 i==6,改变j为j1=1,{B[0]……B[j1]}=={B[j1+1]……B[j-1]}就是{B[0]B[1]}=={B[2]B[3]}。
i 0 1 2 3 4 5 6 7 ……
A a b a b a b e f ……
B a b a b a g
j 0 1 2 3 4 5
这时发现仍然A[6]!=B[2]。
第四步 由于仍然A[6]!=B[2]继续改变j为j1==0。{B[0]……B[j1]}=={B[j1]……B[j-1]}就是{B[0] }=={B[0] }。
i 0 1 2 3 4 5 6 7 ……
A a b a b a b e f ……
B a b a b a g
j -1 0 1 2 3 4 5
这时发现仍然A[6]!=B[0]。继续移动i,让i自增,直到再重复前面四步。
下面再来看j1是如何预先计算出来的。可以用一个数组P[j]来记录j1。因为P[j]与主串没有关系,是由模式串自身的特征决定的,所以可以预先计算出来。
j==0的时候B的子串为B[0]=a 又因为B[0]== B[0](对称)所以P[0]==-1(下标为-1表示B的首字符前一个假想的没有的字符)。
j==1的时候 B的子串B[0]B[1]=ab 向前找到对称串找到B[0]== B[0]所以P[1]==-1。
j==2的时候B的子串B[0]B[1]B[2]=aba向前找对称串,找到B[0]==B[2]所以P[2]==0
j==3 的时候B的子串B[0]B[1]B[2]B[3]=abab向前找对称串,找到B[0]B[1]== B[2]B[3]所以P[3]==1。
j==4的时候B的子串B[0]B[1]B[2]B[3]B[4]=ababa向前找对称串,找到B[0]B[1]B[2]== B[2]B[3]B[4],所以P[4]==2。
j==5的时候B的子串B[0]B[1]B[2]B[3]B[4]B[5]== ababag向前找对称串,找到B[0] == B[0],所以P[5]==-1。
B a b a b a g
j -1 0 1 2 3 4 5
P[j] -1 -1 0 1 2 -1
由此来确定P[i]数组。
代码示例
public class TestKMP
{
char[] A={‘a’,’b’,’a’,’b’,’a’,’b’,’e’,’f’};
char[] B={‘a’,’b’,’a’,’b’,’a’,’g’};
public int[] P = new int[6];
public void initP()
{
int i=-1;
int j=0;
P[0]=-1;
while(j<B.length)
{
if( -1 == i || B[i] == B[j])
{
i++;
j++;
if( j < B.length )
{
P[j]=i;
}
}
else
{
i=P[i];
}
}
}
public int processKmp()
{
int i=0;
int j=0;
int lA=A.length;
int lB=B.length;
while( i < lA && j < lB )
{
if( -1 == j || A[i] == B[j] )
{
i++;
j++;
}
else
{
j=P[j];
}
}
if( j == lB )
{
return i-lB;
}
else
{
return -1;
}
}
/**
* @param args
*/
public static void main(String[] args)
{
TestKMP testKMP = new TestKMP();
testKMP.initP();
System.out.println(testKMP.processKmp());
}
}