写在前面:很多网站的文章里介绍到KMP算法时都会用简单,容易理解来形容,实际上,并不是这样的。
因此,对于要了解该算法,无论你接下来看不看这篇文章,都要给自己准备加起来至少有半天的时间来学习KMP算法,不要轻敌嘿嘿。
这里也只是根据我的理解做的笔记,希望了解的更多请直接移步《算法导论》或者参考资料里的链接。
我个人对于该方法的总结是:
通过关键字自身的比较进行预处理,得到当不匹配时的偏移量(朴素算法是1)。
一直要记住的就是,当两个字符串前n位相同时,如果第n+1位相同,则两个字符串前n+1位还是相同的...是不是太简单~~
KMP算法是对朴素的搜索算法的改进,因此,在学习KMP搜索算法前,先来介绍下朴素的搜索算法。
不妨设文本
S = "aaaaaaaaab"
关键字
T = "aaaab"
朴素的搜索算法,想法很简单,从s的下标0开始,逐个与T比较,成功则返回,否则s的下标+1,继续逐个与T比较,直到结束或返回。
for( int i = 0; i < Len[S]; i++)
{
     int j;
     for(j = 0; j < Len[T]); j++)
    {
         if(S[i+j] != T[j])  break;
    }
     if(j == Len[T])
        find T  in S
}

如果不是很理解,看个直观的图,其中第一个行是S,第二行是T,可以看成一个T不断向后滑动的过程。

a

a

a

a

a

a

a

a

a

b

 

a

a

a

a

b

 

 

 

 

 

 

a

a

a

a

b

 

 

 

 

 

 

a

a

a

a

b

 

 

 

 

 

 

a

a

a

a

b

 

 

 

 

 

 

a

a

a

a

b

比较到第5列时,发现不匹配,于是倒回到上一个下标开始的地方+1处(第三行),继续,如此循环往复。
从这里不难看出,这种情况下算法复杂度为O(S.len*T.len).
KMP算法的改进点就在这里:
第二行不匹配后是否我们应当倒回到第三行的状态继续比较?如果不用,那么应该怎么比较。
我们先用这个例子来表示一下,S[0,4]表示的是S字符串下标从0到4的闭区间。
提前提示一下: T[0,2] = T[1,3]
第二行里,我们比较到了第5列(即下标为4的地方,接下来我们开始只用下标描述,以免产生混乱),即前面的字符串是匹配的,因此有这个结果:
S[0,3] = T[0,3],S[1,3] = T[1,3],S[2,3] = T[2,3]...
同时根据前面的提示:
T[0,2] = T[1,3] = S[1,3]
可以看到T[0,2] = S[1,3]就是第三行里前面要比较的内容,也就是说实际上是不需要的。
第二行失败后,我们可以直接从S[4]与T[3]比较!
也就是说KMP要解决的就是:如果在S的某个下标处不匹配了,那么S的改下标不需要退回,而是选择T的一个下标,来继续比较(因为像前面的例子,T[0,2]=T[1,3]了已经)。
简单直观点的说法,就是,当不匹配时,T不是直接滑动回去再前进一步,而是选择一个可以前进的最大值,同时,直接进行与S当前不匹配下标的比较。
注意这个最大值不一定为1,S当前选择与T的哪个下标比较,会决定这样一个前进值。
再看个例子:

a

b

a

d

e

a

b

a

d

f

a

b

a

d

f

 

 

 

 

 

 

a

 

 

 

 

 

 

 

 

 

 

a

b

 

 

 

 

 

 

 

 

 

a

b

 

 

 

 

 

这是朴素的匹配算法的策略,其中S="abadeabadf",T="abadf"
而KMP的策略是:

a

b

a

d

e

a

b

a

d

f

a

b

a

d

f

 

 

 

 

 

 

 

 

 

 a

b

a

d

f


为什么可以这样选择?
因此,kmp就是要计算这样一个数组next[], 当文本S在其下标i处与T不匹配时,来决定与T哪个下标处开始继续比较。
我们先看下程序:
/*
 * =====================================================================================
 *       Filename:  kmpsearch.c
 *    Description:  kmp search
 *
 *        Version:  1.0
 *        Created:  09/10/2012 07:15:07 PM
 *
 *         Author:  zhy (), [email protected]
 * =====================================================================================
 
*/

/* *
 * @brief KMPSearch kmp搜索字符串算法
 *
 * @param src 待搜索的文本
 * @param srcLen 待搜索文本长度
 * @param keyword 搜索关键字
 * @param keywordLen 搜索关键字长度
 *
 * @return 搜索到第一个结果停止,返回关键字在文本出现的索引,否则返回-1
 
*/
int KMPSearch( const  char* src,  const  int srcLen,  const  char* keyword,  const  int keywordLen)
{
     // 存储kmp需要的next数组
     char* next = ( char*)malloc(keywordLen* sizeof( char));
     if (!next)
         return -1;

     // 初始化next数组的值
    CalcKMPNext(keyword, keywordLen, next);

     int i = 0, j = 0;
     for ( ; i<keywordLen; ++i)
        printf("next: %d\n", next[i]);
    i = 0;
     while (i < srcLen)
    {
         // 如果比较结果不同,则需要回退关键字的索引,继续比较,如果回退至关键字开始处(j==0)时break,下次直接从当前i与关键字的第一个字符开始比较
         while (j >= 0 && src[i] != keyword[j])
            j = next[j];
        printf("i=%d,j=%d\n",i,j);
        i++;
        j++;
         if (j == keywordLen)
             return i - keywordLen;
    }

     return -1;
}

/* *
 * @brief CalcKMPNext 计算kmp算法需要的next数组
 *
 * @param keyword 关键字
 * @param keywordLen 关键字长度
 * @param next 存储next
 
*/
void CalcKMPNext( const  char* keyword,  const  int keywordLen,  char* next)
{
     int i = 0, j = -1;
    next[i] = j; // 初始化next[0],没有什么意义
     while (i < keywordLen)
    {
         // 如果不相等,回退直到相等,或者没有相等
         while (j >= 0 && keyword[i]!=keyword[j])
            j = next[j];
        ++i;
        ++j;
         if (i<keywordLen)
            next[i] = j;
    }
}

int main()
{
      char text[] = "abadeabadf";
      char keyword[] = "abadf";
     /*
     *char text[] = "this is a demo, is is not u , isnot it?";
     *char keyword[] = "is";
     
*/
    unsigned  int textLen = strlen(text);
    unsigned  int keywordLen = strlen(keyword);

     int startIndex = 0;
     while (1)
    {
         int foundIndex = KMPSearch(text + startIndex, textLen, keyword, keywordLen);
         if (foundIndex == -1)
             break;
        startIndex += foundIndex;
        printf("find in %d\n", startIndex);
        startIndex += keywordLen;
    }

     return 0;
}


参考资料:
http://www.inf.fh-flensburg.de/lang/algorithmen/pattern/kmpen.htm 
http://chaoswork.com/blog/2011/06/14/kmp%E7%AE%97%E6%B3%95%E5%B0%8F%E7%BB%93/ 
http://www.matrix67.com/blog/archives/115