KMP算法分析

来源:互联网 发布:java并发编程面试题 编辑:程序博客网 时间:2024/05/22 08:06

为什么会产生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 substring
int pos = 1; // the current position we are computing in T
for (; pos < W.length; cnd++, pos++) {
if (W[pos] == W[cnd]) {
T[pos] = T[cnd];
} else {
T[pos] = cnd;
// prepare for next pos
cnd = 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

 

 

 

原创粉丝点击