KMP算法
字符串比较算法中KMP算法大名鼎鼎,其主要思想是先进行预处理,对模式字串提取出一些有用的信息,然后再进行字符串比较,就可以减少比较的次数。
而KMP的关键是对模式串的有用信息的提取,即提取什么特征值,提取出来后可以加速字符匹配的过程。
下面举个例子来说明究竟应该提取什么特征值。
字符串:abcabaabc
模式串:abaa
序列 | a | b | c | a | b | a | a | b | c |
|
Index=0 | a | b | a |
|
|
|
|
|
|
|
Index=1 |
| a |
|
|
|
|
|
|
| 可用信息 |
Index=2 |
|
| a |
|
|
|
|
|
|
|
Index=3 |
|
|
| a | b | a | a |
|
|
|
Index=4 |
|
|
|
| a |
|
|
|
|
|
Index=5 |
|
|
|
|
| a | b |
|
|
|
对于index=0的比较,此时ab相等,那接下去的index=1是没有必要了,可以省去了,因为前两个字符是ab是匹配的,说明序列有2个字符ab,那接下去再从模式字符串头开始匹配度的话,如果头不是a了,那么就可以跳过index=1这次,从index=2比较,故这个特征信息的关键是什么呢?
序列 | a | b | c | a | b | a | a | b | c |
|
Index=0 | a | b | a |
|
|
|
|
|
|
|
Index=1 |
| a |
|
|
|
|
|
|
| 可用信息 |
以下以一个模式串ababbaaba说明
模式串 | a | b | a | b | b | a | a | b | a |
0 |
|
|
|
|
|
|
|
|
|
1 |
|
|
|
|
|
|
|
|
|
2 |
|
| a |
|
|
|
|
|
|
3 |
|
| a | b |
|
|
|
|
|
4 |
|
|
|
|
|
|
|
|
|
5 |
|
|
|
|
| a |
|
|
|
6 |
|
|
|
|
|
| a |
|
|
7 |
|
|
|
|
|
| a | b |
|
8 |
|
|
|
|
|
|
|
| a |
其中蓝色表示没有匹配的字符,绿色表示匹配的字符,那这就是特征信息了,以后当进行字符串匹配的时候,就可以利用预先提取的特征值了。
特征值的取法
以下说明两个说法
Overlay
假设现在匹配字符串Pattern中已经匹配到了P[j-1],即P[0…j-1]都已经匹配了,但是P[j]不匹配了,
T[0] | T[1] | ……….. |
| T[i-j] | …… | T[i-2] | T[i-1] | T[i] |
|
|
|
|
| P[0] | ….. | P[j-2] | P[j-1] | P[j] |
|
|
|
|
|
| ….. | P[k-2] | P[k-1] | P[k] |
|
此时如果知道了P[j-1]处于前面的P[0..k-1]都匹配,则可以直接比较P[k]和T[i]了,此时提取的模式串的特征值是:overlay[j-1]=k-1],表示P[0….k] = P[i-k…i]最大的k值,即模式串最前几个和最后几个字符相同。
Next
假设现在匹配字符串Pattern中已经匹配到了P[j-1],即P[0…j-1]都已经匹配了,但是P[j]不匹配了,
T[0] | T[1] | ……….. |
| T[i-j] | …… | T[i-2] | T[i-1] | T[i] |
|
|
|
|
| P[0] | ….. | P[j-2] | P[j-1] | P[j] |
|
|
|
|
|
| ….. | P[k-2] | P[k-1] | P[k] |
|
此时我们取得P[j]的特征值,即next[j]=k,表示P[j-k….j-1] = P[0…k-1],现在比较P[j]和P[k]。
在kmp算法中,根据取得特征值的不同,在进行字符串匹配的时候会稍有不同,具体的算法如下:
Overlay方法:
// 使用overlay方法
int kmp_find1( const char *text, const char *pattern )
{
if ( text == NULL || pattern == NULL )
return -1;
int lenT = strlen(text);
int lenP = strlen(pattern);
int *overlay = new int[lenP];
getOverlay( pattern, overlay );
int i,j,s;
i = j = 0;
s = -1;
while ( i<lenT && j<lenP ) {
if ( text[i] == pattern[j] ) {
++i;
++j;
}
else {
if ( j == 0 ) {
++i;
}
else {
j = overlay[j-1] + 1;
}
}
}
if ( j == lenP ) {
s = i - lenP;
}
delete[] overlay;
return s;
}
Next方法:
// 使用next方法
int kmp_find2( const char *text, const char *pattern )
{
if ( text == NULL || pattern == NULL )
return -1;
int lenT = strlen(text);
int lenP = strlen(pattern);
int *next = new int[lenP];
getNext( pattern, next );
int i,j;
i = j = 0;
while ( i < lenT && j < lenP ) {
if ( j == -1 || text[i] == pattern[j] ) {
++i; ++j;
}
else {
j = next[j];
}
}
delete[] next;
if ( j == lenP )
return i - lenP;
else
return -1;
}