下面是一个C代码实现的KMP算法,算法最困难的地方是计算出next数组,里面记录了匹配字符串各个位上的值。这个值可以叫做“部分匹配值”或“覆盖率”。
要理解KMP算法首先要知道“朴素字符串匹配”算法,就是从头到尾比较字符,如果相等就同时后移,如果不相等则源字符串的搜索位置后移一位,然后重新匹配一遍。KMP算法是对朴素匹配算法的改进,提出了一个很抽象的概念:部分匹配值。这个概念的意思是:在一个串里面前缀和后缀的最长的子串的长度。所以要理解KMP还要明白前缀和后缀指的是什么。
建议仔细阅读阮一峰的 博客,写得图文并茂通俗易懂,或者图灵社区的 文章。少啰嗦,先看东西:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 100
void cal_next( char * str, int * next, int len )
{
int i, j;
next[0] = 0;
for( i = 1; i < len; i++ )
{
j = next[ i - 1 ];
while( str[ j ] != str[ i ] && j > 0 )
{
j = j - 1;
}
if( str[ i ] == str[ j ] )
{
next[ i ] = j + 1;
}
else
{
next[ i ] = 0;
}
}
}
//返回值:模式字符串在源字符串第一次匹配的位置
int KMP( char * str, int slen, char * ptr, int plen, int * next )
{
int s_i = 0, p_i = 0;
while( s_i < slen && p_i < plen )
{
if( str[ s_i ] == ptr[ p_i ] )
{
s_i++;
p_i++;
}
else
{
if( p_i == 0 )
{
s_i++;
}
else
{
p_i = next[ p_i - 1 ];
}
}
}
return ( p_i == plen ) ? ( s_i - plen ) : -1;
}
int main()
{
char str[ N ] = "ABCDAB ABCDABE ABCD";
char ptr[ N ] = "ABAABCABA"; //next: 0 0 1 1 2 0 1 2 3
//char ptr[N] = "ABCDAB"; //next: 0 0 0 0 1 2
//char ptr[N] = "AABCABCD"; //next: 0 1 0 0 1 0 0 0
int slen, plen;
int next[ N ];
slen = strlen( str );
plen = strlen( ptr );
cal_next( ptr, next, plen );
printf( "matched at: %d
", KMP( str, slen, ptr, plen, next ) );
return 0;
}
反思:
由于朴素匹配算法在匹配失败的时候要从模式字符串的第一个字符重新开始,所以朴素匹配算法的时间复杂度是O(n*m)。而KMP有效的避免了重复搜索,所以时间复杂度是O(n+m)。