KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。
研究了两天依然懵懵懂懂 _(:зゝ∠)_,索性把自个理解的贴上来找点感觉。
传统暴力匹配
void NativeStrMatching( ElemType Target[], ElemType Pattern[] )
{
register int TarLen = 0; // Length of Target
register int PatLen = 0; // Length of Pattern
// Compute the length of Pattern
while( '\0' != Pattern[PatLen] )
PatLen++;
while( '\0' != Target[TarLen] )
{
int TmpTarLen = TarLen;
for(int i=0; i<PatLen; i++)
{
if( Target[TmpTarLen++] != Pattern[i] )
break;
if( i == PatLen-1 )
cout<<"Native String Matching,pattern occurs with shift "<<TarLen<<endl;
}
TarLen++;
}
}
传统的匹配方法通过从Target[0]开始循环不停的遍历Pattern妄图与Target进行匹配。如若失配则从Target[1]继续重复上一步骤,直到整个Pattern在Target中找到匹配,或者已经扫描完整个目标串也没能够完成匹配为止。
这样的操作简单,容易实现,但是大多数情况下会出现很多多余的判断。
比如假设Target=“abcabdaabcdef”
Pattern=“abcd”
如传统匹配方法,我们要逐一进行一下步骤
然而在第一次匹配中,target和pattern的前三个字符匹配成功,而第
四个字符失配。继而指针移动到target[1]继续进行匹配。也就是第二步。
咱们肉眼可以看出,在pattern中,pattern[0] != pattern[2] != pattern[3],并且target[0] = pattern[0] …..target[3] = pattern[3]。
所以pattern[1]和[2] 绝对不可能匹配到target[1]和[2],所以2、3步骤其实是多余了,而KMP算法就是用代码实现我们肉眼所能判断到的多余的步骤并跳过它。
KMP算法
在失配后,并不简单地从目标串下一个字符开始新一轮的检测,而是依据在检测之前得到的有用信息,直接跳过不必要的检测,从而达到一个较高的检测效率。
当第一次失配后,并不直接开始实施第二步,而是通过一些信息,直接跳过后几个肯定不可能匹配的冗余字符,而直接让模式串Pattern从目标串的target[3]开始新一轮的检测,从而达到了减少循环次数的效果。
覆盖函数
void CptPfFunc(char Pattern[], int PrefixFunc[])
{
register int iLen = 0; // Length of Pattern[]
while ('\0' != Pattern[iLen])
iLen++;
int LOLP = 0; // Lenth of longest prefix
PrefixFunc[1] = 0;
for (int NOCM = 2; NOCM<iLen + 1; NOCM++) // NOCM represent the number of characters matched
{
while (LOLP>0 && (Pattern[LOLP] != Pattern[NOCM - 1]))
LOLP = PrefixFunc[LOLP];
if (Pattern[LOLP] == Pattern[NOCM - 1])
LOLP++;
PrefixFunc[NOCM] = LOLP;
}
}
覆盖函数所表征的是pattern本身的性质,可以让为其表征的是pattern从左开始的所有连续子串的自我覆盖程度。
理解KMP的核心就在于理解覆盖函数。简单地说覆盖函数的作用就是标示头尾相同字符串的索引位置(从0开始)。如下:
func(a) = -1 #没有对称字符
func(ab) = -1
func(aba) = 0
func(abaa) = 0
func(abaab) = 1 #ab首尾相同
func(abaabcaba) = 2 #aba首尾相同
上个实例:
比如字符串: “abccabccabca”
NOCM 表示 已经匹配的字符数
LOLP 表示 既是自身真后缀又是自身最长前缀的字符串长度
以下是计算流程:
LOLP | NOCM | LOLP | PrefixFunc |
---|---|---|---|
0 | 2 | 0 | PrefixFunc[2] = 0 |
0 | 3 | 0 | PrefixFunc[3] = 0 |
0 | 2 | 0 | PrefixFunc[4] = 0 |
0 | 4 | 0 | PrefixFunc[5] = 0 |
0 | 5 | 1 | PrefixFunc[5] = 1 |
1 | 6 | 2 | PrefixFunc[6] = 2 |
2 | 7 | 3 | PrefixFunc[7] = 3 |
3 | 8 | 4 | PrefixFunc[8] = 4 |
4 | 9 | 5 | PrefixFunc[9] = 5 |
5 | 10 | 6 | PrefixFunc[10] = 6 |
6 | 11 | 7 | PrefixFunc[11] = 7 |
7 | 12 |
最后我们可以得出PrefixFunc[] = { 0,0,0,0,1,2,3,4,5,6,7,1 }
KMP
void KMPstrMatching( ElemType Target[], ElemType Pattern[] )
{
int PrefixFunc[MAX_SIZE];
register int TarLen = 0;
register int PatLen = 0;
// Compute the length of array Target and Pattern
while( '\0' != Target[TarLen] )
TarLen++;
while( '\0' != Pattern[PatLen] )
PatLen++;
// Compute the prefix function of Pattern
CptPfFunc( Pattern, PrefixFunc );
int NOCM = 0; // Number of characters matched
for( int i=0; i<TarLen; i++ )
{
while( NOCM>0 && Pattern[NOCM] != Target[i] )
NOCM = PrefixFunc[NOCM];
if( Pattern[NOCM] == Target[i] )
NOCM++;
if( NOCM == PatLen )
{
cout<<"KMP String Matching,pattern occurs with shift "<<i - PatLen + 1<<endl;
NOCM = PrefixFunc[NOCM];
}
}
}
解决了覆盖函数KMP算法就没什么了,如上代码所示。
当字符串失配时,咱的Target就不动了,只需要通过覆盖函数求出的PrefixFunc移动pattern就好。
而当pattern[0] != target[x] 时,就移动target咯。
懒得画了盗个图:
ps: 图中有一个错误,index=8时不应该跳过
补一个自己写的案例吧
#include <iostream>
using namespace std;
#define MAXSIZE 64
void getNextVal(const char T[], int next[])
{
int i = 1;
int j = 0;
next[1] = 0;
while (T[i])
{
// 如果是开头或者匹配成功的话双方向前移动
if (j == 0 || (T[i] == T[j]))
{
++i;
++j;
//
if (T[i] != T[j])
{
//
next[i] = j;
}
else
{
// 如果还是相等
next[i] = next[j];
}
}
// 如果不匹配,则寻找T[next[j]]与T[i]进行匹配
else
{
j = next[j];
}
}
}
void Kmp(const char *target, const char *pattern)
{
int next[MAXSIZE];
int tarlen = 0;
int patlen = 0;
while ('\0' != target[tarlen])
{
tarlen++;
}
while ('\0' != pattern[patlen])
{
patlen++;
}
getNextVal(pattern, next);
int j = 0;
for (int i = 0; i < tarlen; ++i)
{
while (j > 0 && pattern[j] != target[i])
j = next[j];
if (pattern[j] == target[i])
j++;
if (j == patlen)
{
cout << "KMP String Matching,pattern occurs with shift " << i - patlen + 1 << endl;
j = next[j];
}
}
}
int main()
{
char* target = "annbcdanacadannabnncsannswawqazcxxxannabnnaaa";
char* pattern = "annabnna";
Kmp(target, pattern);
system("pause");
return 0;
}
参考博客:
http://blog.csdn.net/power721/article/details/6132380 (图片来源)
参考视频:
上:http://v.youku.com/v_show/id_XODYxNjExODQ=.html 第 34分钟开始
下:http://www.56.com/u28/v_NjAwMzA0ODA.html
严蔚敏 // 其实大多数是从视频上搞清楚的,推荐大家看一下视频