写在前面:很多网站的文章里介绍到KMP算法时都会用简单,容易理解来形容,实际上,并不是这样的。
因此,对于要了解该算法,无论你接下来看不看这篇文章,都要给自己准备加起来至少有半天的时间来学习KMP算法,不要轻敌嘿嘿。
这里也只是根据我的理解做的笔记,希望了解的更多请直接移步《算法导论》或者参考资料里的链接。
我个人对于该方法的总结是:
通过关键字自身的比较进行预处理,得到当不匹配时的偏移量(朴素算法是1)。
一直要记住的就是,当两个字符串前n位相同时,如果第n+1位相同,则两个字符串前n+1位还是相同的...是不是太简单~~
KMP算法是对朴素的搜索算法的改进,因此,在学习KMP搜索算法前,先来介绍下朴素的搜索算法。
不妨设文本
S = "aaaaaaaaab"
关键字
T = "aaaab"
朴素的搜索算法,想法很简单,从s的下标0开始,逐个与T比较,成功则返回,否则s的下标+1,继续逐个与T比较,直到结束或返回。
如果不是很理解,看个直观的图,其中第一个行是S,第二行是T,可以看成一个T不断向后滑动的过程。
比较到第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的哪个下标比较,会决定这样一个前进值。
再看个例子:
这是朴素的匹配算法的策略,其中S="abadeabadf",T="abadf"
而KMP的策略是:
为什么可以这样选择?
因此,kmp就是要计算这样一个数组next[], 当文本S在其下标i处与T不匹配时,来决定与T哪个下标处开始继续比较。
我们先看下程序:
参考资料:
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
因此,对于要了解该算法,无论你接下来看不看这篇文章,都要给自己准备加起来至少有半天的时间来学习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
}
{
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 |
从这里不难看出,这种情况下算法复杂度为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 |
|
|
|
|
|
而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;
}
* =====================================================================================
* 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