相信很多人(包括自己)初识KMP算法的时候始终是丈二和尚摸不着头脑,要么完全不知所云,要么看不懂书上的解释,要么自己觉得好像心里了解KMP算法的意思,却说不出个究竟,所谓知其然不知其所以然是也。
-----------------------------------谨以此文,献给刚接触KMP算法的朋友,定有不足之处,望大家指正----------------------------------------
【KMP算法简介】
【传统字符串匹配算法的缺憾】
请看以下传统字符串匹配的代码:
C++ code
void NativeStrMatching( ElemType Target[], ElemType Pattern[])
{
registerint TarLen =0;
//Length ofTarget
registerint PatLen =0;
//Length ofPattern
//Compute the length ofPattern
while('\0' != Pattern[PatLen])
PatLen++;
while('\0' != Target[TarLen])
{
intTmpTarLen =TarLen;
for(inti=0; i
{
if(Target[TmpTarLen++] != Pattern[i])
break;
if(i == PatLen-1)
cout<<"NativeString Matching,pattern occurs with shift"<<TarLen<<endl;
}
TarLen++;
}
}
{
}
【代码思想】
不妨假设我们的目标串
Target =
"a b c d e a b c d e a b c df"
需要匹配的模式串
Pattern = "c d f";
那么当匹配到如下情况时
由于 'e' != 'f' ,因此失配,那么下次匹配起始位置就是目标串的'd'字符
我们发现这里照样失配,直到运行到下述情况
也就是说,中间的四个字符 d e a b完全没有必要检测,直接跳转到下一个'c'开始的地方进行检测
【KMP算法的引入】
【KMP算法思想详述与实现】
下面分为两个板块分别详述:
【前缀函数的引入及实现】
【前缀函数的引入】
理解了什么是前、后缀,就来看看什么是前缀函数:
【前缀函数的实现】
下面就来分析怎么用代码来表达这种关系。
这里采用《算法导论(第二版)》中的思想求解。
不妨以 PrefixFunc[] 表示这个前缀函数,那么我们将得到以下求前缀函数的函数:
由于 0个匹配字符数在计算中没有意义,因此PrefixFunc下标从1开始,也就是从已经有一个字符(即首字符)匹配的情况开始
C++ code
// Compute Prefixfunction
void CptPfFunc( ElemType Pattern[], int PrefixFunc[])
{
void CptPfFunc( ElemType Pattern[], int PrefixFunc[])
{
}
NOCM 表示 已经匹配的字符数
LOLP 表示 既是自身真后缀又是自身最长前缀的字符串长度
以下是计算流程:
PrefixFunc[1] = 0; //只匹配一个字符就失配时,显然该值为零
LOLP = 0;
NOCM =2;
LOLP =0;
PrefixFunc[2] = 0;
LOLP = 0;
NOCM =3;
LOLP =0;
PrefixFunc[3] = 0;
LOLP = 0;
NOCM =4;
LOLP =0;
PrefixFunc[4] = 0;
LOLP = 0;
NOCM =5;
LOLP =1;
PrefixFunc[5] = 1;
LOLP = 1;
NOCM =6;
LOLP =2;
PrefixFunc[6] = 2;
LOLP = 2;
NOCM =7;
LOLP =3;
PrefixFunc[7] = 3;
LOLP = 3;
NOCM =8;
LOLP =4;
PrefixFunc[8] = 4;
LOLP = 4;
NOCM =9;
LOLP =5;
PrefixFunc[9] = 5;
LOLP = 5;
NOCM = 10; LOLP= 6;
PrefixFunc[10] = 6;
LOLP = 6;
NOCM = 11; LOLP= 7;
PrefixFunc[11] = 7;
LOLP = 7;
NOCM = 12;
---------此时满足条件while( LOLP>0 && (Pattern[LOLP] !=Pattern[NOCM-1]) )-------------
while语句中的执行
{
}
LOLP = 0;
最后我们的前缀函数 PrefixFunc[] = { 0,0,0,0,1,2,3,4,5,6,7,1 }
其间最精妙的要属失配时的操作
while( LOLP>0 && (Pattern[LOLP] != Pattern[NOCM-1]))
LOLP = PrefixFunc[LOLP];
其中 LOLP = PrefixFunc[LOLP];
递归调用PrefixFunc函数,直到整个P字串都再无最长前缀或者找到一个之前的满足条件的最长前缀。
由以上分析,不难推导KMP算法的实现
C++ code
void KMPstrMatching( ElemType Target[], ElemType Pattern[])
{
intPrefixFunc[MAX_SIZE];
registerint TarLen =0;
registerint PatLen =0;
//Compute the length of array Target andPattern
while('\0' != Target[TarLen])
TarLen++;
while('\0' != Pattern[PatLen])
PatLen++;
//Compute the prefix function ofPattern
CptPfFunc(Pattern, PrefixFunc);
intNOCM =0;
// Number of charactersmatched
for(int i=0; i
{
while(NOCM>0 && Pattern[NOCM] != Target[i])
NOCM=PrefixFunc[NOCM];
if(Pattern[NOCM] == Target[i])
NOCM++;
if(NOCM == PatLen)
{
cout<<"KMPString Matching,pattern occurs with shift "<<i - PatLen +1<<endl;
NOCM=PrefixFunc[NOCM];
}
}
}
{
}
【参考文献】
《Introduction to Algorithms》Second Edition
by Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest andClifford .
by Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest andClifford .
本文出自 “Bill_Hoo专栏”博客,请务必保留此出处http://billhoo.blog.51cto.com/2337751/411486