算法简介
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特---莫里斯---普拉特操作。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复制度是O(m+n)。
导入
对于字符串匹配算法大家第一个想到的可能就是暴力匹配(我就是这么想的),比如一个主串:ABABCABABA,模式串ABABA
定义一个指向主串s的指针i,一个指向模式串的指针j,上面可以看出,每次如果未匹配都需要回溯指针i,其中有很多的回溯都是不必要的,浪费太多的时间,能不能找到一种方法,不需要回溯指针i,用较少的时间开销来进行匹配呢,嗯这就是看毛片算法啦(KMP)。要想了解KMP算法我们需要先知道Next数组,也可以叫做prefix_table。
prefix_table求解
prefix_table只和模式串有关和主串无关,假设模式串为T:ABABA
前缀表的求解过程
下标是0的地方恒填入0,然后依次分割模式串,比如这里面,分割成AB、ABA、ABAB、ABABA分别找出他们的最长前缀和后缀的长度,例如:对于ABAB,他的最长前缀是ABA、最长后缀是BAB,比较后发现他们不匹配,那么找前缀为AB、后缀就为BA发现还是不匹配,则找前缀A、后缀A发现匹配,所以此处的长度就是1;
下面是prefix_table的代码
void prefix_table(char pattern[], int prefix[], int length)
{
prefix[0] = 0;
int len = 0;
int i = 1;
while (i < length)
{
if (pattern[i] == pattern[len])
{
len++;
prefix[i] = len;
i++;
}
else
{
//斜对一格,处理最后一次匹配,见注(1)
if (len > 0)//避免数组越界
{
len = prefix[len-1];
}
//处理第一次匹配AB卡死情况;
else
{
prefix[i] = len;
i++;
}
}
}
}
注(1):此出涉及到一个问题的处理,我们假设现在的模式串改成了ABABAA,考虑最后一次匹配的过程
此时的len==3,说明前面匹配的最长公共前后缀是3,也就是ABA,如果此时我们用pattern[len]和patter[5]比较会发现不等,我们直接填入0,但是发现最后一个A是可以和最前面的A匹配的,所以此时需要一点策略,可以采用斜对角的方式
len = prefix[len-1],如果还是不等,继续向前迭代,总会遍历到所有的模式串len长度前的所有字符的,这样就解决了最后一次的冲突问题。
为了后续KMP算法实现的简化,我们优化一个prefix_table把,prefix_table整体右移一位,并另前面prefix_table[0] == -1;
右移动代码
void move_prefix_table(int prefix[],int n)
{
for (int i = n-1; i > 0; i--)
{
prefix[i] = prefix[i-1];//整体向右边移动一位
}
prefix[0] = -1;//把第一个prefix_table[0]的值设置成-1
}
KMP算法
void kmp_search(char s[], char pattern[])
{
int n = strlen(pattern);
int m = strlen(s);
int *prefix = new int[n];
prefix_table(pattern, prefix, n);
move_prefix_table(prefix,n);
int i = 0, j = 0;
while (i < m)//主串指针小于主串长度,因为KMP算法i指针不回溯
{
if (j == n - 1 && s[i] == pattern[j])
{
cout << "located in " << i - j << endl;//匹配到
j = prefix[j];//如果主串中还有相同的,继续后续匹配
}
if (s[i] == pattern[j])//比较主串和模式串的字符,如果相等则把i和j指针右移一位继续比较
{
i++;
j++;
}
else
{
j = prefix[j];//未匹配s[i]和patter[j],那么把j指针移动到prefix_table中数值对应的位置上继续后续匹配
if (j == -1) //匹配到prefix[0] == -1这个位置,需要把i和j指针整体右移一位
{
j++;
i++;
}
}
}
}
测试代码
int main()
{
char pattern[] = "ABABA";
char s[] = "ABABCABABA";//下标是5的位置匹配上了
kmp_search(s, pattern);
return 0;
}