由于不是计算机专业,数据结构课上老师对于这么算法草草而过。 今天做到一个题目的时候需要用到这个算法,今天就来好好学一学。
模式匹配最简单直接的办法是BF算法,我个人可以理解为Brutal force吧。暴力以主串的每个位置为起点,逐个与模式进行匹配。由于主串的指针i可能需要回溯,所以这个算法在最坏情况下的时间复杂度可以达到O(m*n)。其中,n和m分别是主串和模式的长度。
KMP算法的优越之处就是设法取消了i指针的回溯,使得i始终只朝着一个方向移动。假设主串和模式分别用数组表示如下:
S[0], S[1], S[2], …… , S[n-1]
T[0], T[1], T[2], …… , S[m-1]
现在要解决的问题是,当T[0,1,2,…,j-1]和S[i,i+1,i+2,…,i+j-1]匹配,但是T[j]和S[i+j]失配时,模式“向右移动”多远能够“可能”再一次匹配。因为根据模式本身的特点,我们可以判断在一些位移的情况下,匹配是不可能成功的。比如,如果模式m为abcabc, 当m在从左往右数第二个“b”的位置失配时,那么由于前面四个字符已经匹配,只进行一个长度的位移或者两个长度的位移都是不可能匹配的。
那么怎么找到这样的第一个“位移”呢?
我们假设此时应该从模式T中的第k(k<j)个字符开始继续比较,那么有一点是肯定的,那就是模式T中的前k-1个字符T[0,1,2,…,k-2]应该与主串的S[i+j-k+2…,i+j-1,i+j]相匹配,并且这个k是满足这个条件的最大的k。
所有我们可以得到: T[0,1,2,…,k-2] = S[i+j-k+1,i+j-k+2,…,i+j-2,i+j-1]
由已经匹配部分结果,我们有: T[0,1,2,…,k-2,k-1,…,j-1] = S[i,i+1,i+2,…,i+k-2,i+k-1,…,i+j-1]
为了表示的方便 我们不妨画个示意图:
T[0] T[1] … T[k-3] T[k-2]
|| || || ||
S[0] S[1] … S[i] S[i+1] S[i+2] … S[i+j-k+1] S[i+j-k+2] … S[i+j-2] S[i+j-1] S[i+j]
|| || || || || || || 不等于
T[0] T[1] T[2] … T[j-k+1] T[j-k+2] … T[j-2] T[j-1]
所有我们有T[0,1,2,…,k-2] = T[j-k+1,j-k+2,…,j-2,j-1] 。所以我们问题实际上转化为求一个当前匹配长度的模式子串的一个前缀,使得这个前缀等于相同长度的模式字串的后缀,并且这个长度是最长的。
为了求这个k,一般的方法是引入一个数组next,next[j]就表示T[j]和S[i+j]失配时,模式T中需要重新和主串S中的S[i+j]进行比较的字符的位置。
那么具体怎么找呢?
采用递归的思想,我们假设已经知道了next[0],next[1],next[2],…,next[j-1]的值,求next[j].。具体见代码
1: int kmp(char* str, char* pat)
2: {
3: int i, j, k;
4: memset(fail, -1, sizeof(fail));
5: i = 0;k=-1;
6: while(pat[i])
7: {
8: if(k == -1 || pat[i] == pat[k])
9: {
10: i++,k++,fail[i] = k;
11: }
12: else k = fail[k];
13: }
14: i = j = 0;
15: while( str[i] && pat[j] )
16: {
17: if( pat[j] == str[i] ) ++i, ++j;
18: else if(j == 0)++i;
19: else j = fail[j];
20: }
21: if( pat[j] ) return -1;
22: else return i-j;
23: }