为什么会产生KMP算法?
问题:给定一下字符串S,找出S中匹配W的起始位置?
解决这个问题,一般来讲,可以从S的第0个字符开始,看与W的第0个字符是否相等,如果相等再比较第1个字符,如果相等继续比较,如果不相等,再回到S的第1个字符,与W的第0个字符相比较。
过程如下图所示:
S:ABCFABCDABFABCDABCDABDE
W:ABCDABD
1. 比较发现W的第3个字符与S的第3个字符不相等,中止比较
2. 从S的第1 个字符开始S的第0个字符开始比较,不相等,中止比较
3. 从S的第2个字符开始比较,不相等,中止比较
4. 从S的第3个字符开始比较……
观察图中现象,第1次比较时,S和W的前三个字符是相等的,那么第二次比较等同于W的第0个字符与W的第1个字符比较,第三次比较等同于W的第0个字符与W的第2个字符比较。
再观察第5个,第5步的W的最后一个字符与S的第10个字符不相等,中止比较。但从比较的过程中,可以确定,W的0-5个字符与S的4-9个字符是相等的。W[0…5] == S[4…9]
第5步和第12步,都是在最后一个字符发现不相等,进而中止比较,然后后面的三步的比较过程是惊人的相似!
有没有办法跳过重复的比较过程? 如第12步,可以直接进入到第16步,第4-5的AB与0-1的AB相等,是不是可以直接从W的第3个字符开始比较
上面的描述未免有点一相情愿,缺乏理论根基,Knuth, Morris, Pratt 三个大神已经给出了处理算法:寻找最长首尾匹配位置,该算法对W串进行预处理
算法描述
依据wikipedia中的描述,重新画出KMP算法的处理流程:
其中
S表示待查找的字符串,
W表示模式字符串,
m表示匹配的模式字符串在S中的起始位置,
i表示比较时W字符串的位置指针
第一步,m = 0, S[3]!=W[3]。 W[0]与W[1]、W[2]均不相等, 所以W[0]与S[1]、S[2]均不相等。调整 m = 3, i = 0;
第二步,S[3] != W[0],从S的下一个位置开始比较,m = 4, i = 0
第三步,S[4, 9] = W[0,5], S[10] != W[6]。W[1-3]中均与W[0]不相等,对应的S[5-7]中不会存在与W[0]匹配的字符,而W[0, 1]与W[4,5]相等,下一步比较S[10]与W[2], 此时m = 10 -2 = 8, i = 2;
第四步,m = 8, i = 2,由于W[0]与S[8]、W[1]与S[9]相等,第四步的比较直接从i=2开始,S[10] != W[2],W[0] !=W[1], W[0] !=S[9], 调整 m = 10, i = 0;
第五步,W[0] !=S[10], 从S的下一个字符开始比较,调整 m = 11, i=0
第六步,同第三步,W[6] != S[11 + 6], 而S[15,16] == W[4,5]==W[0,1], 调整m = 15,i = 2;
第七步,找到匹配的W,在S中的起始位置为15。
假设存在局部匹配表T,用于指示当前字符比较失败后下一次比较从哪开始。T的每一项构造满足如下关系: 从S[m]开始比较,当S[m+i]不等于W[i]时,比较从S[m+ i – T[i]]字符串开始。这里有两层暗示:
1. T[0] = -1,即W[0]不匹配的时候,S不需要回溯,需要从下一个字符开始比较。
2. 尽管下一个可能匹配开始的节点起点在 m + i – T[i], 我们并不需要实际check该起点后T[i]个字符,我们继续从W[T[i]]开始搜索。(参见上面第6步描述)。
为什么不需要check前T[i]个字符?
S[m + i] != W[i]中断比较,而前面的字符是相等的,即S[0…m+i-1] == W[0…i-1],在W中 W[0…T[i]-1] == W[i-T[i]…i-1],所以有 S[m+i-T[i]…m+i-1]== W[0…T[i]-1], 进而推出,比较从S[m+i]、W[T[i]]开始。
以上算法 ,可以用于查找匹配的位置,那么下一步需要解决T如何求取。
T[i]表征的含义:当S[m+i]与W[i]不相等时,再次比较的发起位置。
1. W的位置i,前面i个字符中存在最长首尾匹配字符串,假设长度为k,如果k==0表示没有最长首尾匹配串。
2. W[i]与W[k]不相等,则T[i] = k。对于i+1时,需要找到一个新的k,使W[i]与W[k]相等,即找到最长首尾匹配串。由于W[0, k-1] == W[i-k,i-1],且T[k]是可能满足最长首尾匹配串的可能的取值,所以令k = T[k],继续迭代,直到W[k] == W[i]或k< 0;
3. 如果W[i]与W[k]相等,则T[i] = T[k]
举例:
i = 0时,T[0] = -1
i = 1时, k =0, W[1] == W[0], T[1]=T[0]=-1
i = 2时, k =1, W[2] != W[1], T[2] = 1, 未找到以W[2]结尾的最长首尾匹配串,k = -1
i = 3时,k = 0,W[3]!=W[0], T[3] = 0,未找到以W[3]结尾的最长首尾匹配串, k = -1
I = 4时,k =0, W[4] == W[0], T[4] = T[0] = -1
I = 5时,k =1, W[5] == W[1], T[5] = T[1] = -1
I = 6时,k =2, W[6] != W[2], T[6] = 2,未找到以W[6]结尾的最长首尾匹配串,k = -1
I = 7时,k =0……T[7] = 0
I = 8时,k =0……T[8] = 0
I = 9时,k =0, W[9] == W[0], T[9] = T[0] = -1
I = 10时,k =1, W[10] == W[1], T[10] = T[1] = -1
I = 11时,k =2, W[11] == W[2], T[11] = T[2] = 1
I = 12时,k =3, W[12] == W[3], T[12] = T[3] = 0
I = 13时,k =4, W[13] == W[4], T[13] = T[4] = -1
I = 14时,k =5, W[14] == W[5], T[14] = T[5] = -1
I = 15时,k =6, W[15] != W[6], T[15] = 6;
T[6] = 2, W[2] == W[15], 所以k = 2
I = 16时,k =3, W[16] != W[3], T[16]=3,
T[3] = 0, W[0] = -1 != W[16], 未找到k使W[k] == W[i], k = -1;
I = 17时,k = 0
代码实现
public class KMP {
private char [] S ;private char [] W ;private int m ;private int i ;private int [] T ;
KMP ( char [] S , char [] W ) {this . S = S ;this . W = W ;m = 0 ;i = 0 ;T = new int [ W . length + 1 ];}
public int find () {generateT ();while ( m + i < S . length ) {while ( i < W . length && S [ m + i ] == W [ i ]) {i ++;}if ( i == W . length ) {return m ;}m = m + i - T [ i ];i = T [ i ] < 0 ? 0 : T [ i ];}return - 1 ;}
public void generateT () {T [ 0 ] = - 1 ;int cnd = 0 ; // the zero-based index in W of the next character of the current candidate substringint pos = 1 ; // the current position we are computing in Tfor (; pos < W . length ; cnd ++, pos ++) {if ( W [ pos ] == W [ cnd ]) {T [ pos ] = T [ cnd ];} else {T [ pos ] = cnd ;// prepare for next poscnd = T [ cnd ];while ( cnd >= 0 && W [ cnd ] != W [ pos ]) {cnd = T [ cnd ];}}}T [ pos ] = cnd ;}
public static void main ( String [] args ) {char [] S = new char [] { 'A' , 'B' , 'C' , ' ' , 'A' , 'B' , 'C' , 'D' , 'A' , 'B' , ' ' , 'A' , 'B' , 'C' , 'D' , 'A' , 'B' , 'C' , 'D' , 'A' , 'B' , 'D' , 'E' };char [] W = new char [] { 'A' , 'B' , 'C' , 'D' , 'A' , 'B' , 'D' };KMP kmp = new KMP ( S , W );System . out . println ( kmp . find ());}
}
参考资料:
https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm
http://www.ituring.com.cn/article/59881