目录:
KMP的起源:
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(KMP就是取三个人名字的首字母作为该算法的名字)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。
简而言之,就是三位信息界的Orz受够了暴力这种低效率的算法,共同研究了KMP这种优化算法,当然,KMP算法的效率要远远高于暴力,至于为什么,请读者大大继续往下看。
BF算法:
BF算法,即暴力(Brute Force)算法,是普通的模式匹配算法,BF算法的思想就是将目标串 s 的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较 s 的第二个字符和 t 的第二个字符;若不相等,则比较 s 的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。
正所谓暴力,当然是:将主串 s 中某个位置i起始的子串和模式串t相比较。即从 j=0 起比较 s[i+j] 与 t[j],若相等,则在主串 t 中存在以 i 为起始位置匹配成功的可能性,继续往后比较( j逐步+1 ),直至与 t 串中最后一个字符相等为止,否则改从 s 串的下一个字符起重新开始进行下一轮的"匹配",即将串 t 向后滑动一位,即 i 增1,而 j 退回至0,重新开始新一轮的匹配。
【代码实现】
int baoli(char *s,char *t)//主串s,模式串t
{
int i=0,j;
while(i<strlen(s))
{
j=0;
while(s[i]==t[j]&&j<strlen(t))//匹配过程
{
i++;//主串右移
j++;//模式串右移
}
if(j==strlen(t))//匹配成功
{
return i-strlen(t);
}
i=i-j+1;//回溯过程
}
return -1;
}
大家都知道,上面的暴力算法是很慢很慢的。你问我为什么?当然是因为它是暴力啦 因为暴力算法中有很多次匹配是重复的。
例如:在串 s =”abcabcabdabba”中查找 t =” abcabd”(我们可以假设从下标0开始):先是比较 s[0] 和 t[0] 是否相等,然后比较 s[1] 和 t[1] 是否相等…我们发现一直比较到 s[5] 和 t[5] 才不等。当这样一个失配发生时,T下标必须回溯到开始,S下标回溯的长度与T相同,然后S下标增1,然后再次比较。这次立刻发生了失配,t 下标又回溯到开始,s 下标增 1 ,然后再次比较。又一次发生了失配,所以 t 下标又回溯到开始,s 下标增1,然后再次比较。这次 t 中的所有字符都和 s 中相应的字符匹配了。函数返回 t 在 s 中的起始下标3。
数据稍微大那么一点点,暴击就会超时,于是,就有了KMP算法。
KMP算法:
KMP算法其实就是一种改进的字符串匹配算法,关键是利用匹配后失败的信息,尽量减少模式串 t 与主串 s 的匹配次数以达到快速匹配的目的。
对于两个字符串,s 表示主串,t 表示模式串,我们用两个指针 i 和 j 分别表示s[i-j+1...i]与t[1...j]完全相等。
KMP的策略就是调整 j 的位置(减小 j 的值)并使 j 尽量大,使得s[i-j+1...i]与t[1...j]保持匹配且尝试匹配s[i+1]与t[j+1]。
如果主串 s 为:a b c a b c d h i j k
模式串 t 为:a b c e
当匹配到主串 s 的第四个字符 A 时,可知 A 和 E 不相等,因此需要移向下一位,但并不需要从模式串中的第一位重新开始比较, 因为主串中的前三个字符已经没有未匹配的 A 了, 不可能匹配成功。

可以发现,j 的取值与 i 无关,只与模式串 t 有关。这样,就可以预处理出一个数组 p[j] ,p[j] 应该是所有满足 t[1...k] = t[j-k+1...j] 的 k(k < j) 的最大值(即最长的相同的前缀后缀),表示当匹配到 t 数组的第 j 个字母而第 j+1 个字母不能完全匹配时,新的 j 最大是多少。
实际上,有可能 j=0 时仍然存在 s[i+1]≠t[j+1]。因此增加 i 的值但忽略 j ,直到 s[i]=t[1]为止。
【代码实现】
inline void kmp()//假设主串 s 的下标从 1 开始
{
for(int i=1,j=0;i<=strlen(s+1);i++)
{
while(j>0&&t[j+1]!=s[i])
{
j=p[j];
}
if(t[j+1]==s[i])
{
j++;
}
if(j==strlen(t+1))
{
sum++;//记录匹配成功的字串数量
}
}
}
现在还剩下一个问题:怎么求 p[i] ?
实际上,我们可以通过 p[1],p[2],... ,p[j-1] 的值来求 p[j] 的值。代码和主程序神似(其实就是模式串 t "自我匹配"的过程)。
【代码实现】
int p[100001];
inline void pre()//假设模式串 t 的下标从 1 开始
{
p[1]=0;//p[1]的值初始为 0
for(int i=1,j=0;i<strlen(t+1);i++)//匹配过程
{
while(j>0&&t[j+1]!=t[i+1])
{
j=p[j];
}
if(t[j+1]==t[i+1])
{
j++;
}
p[i+1]=j;
}
}
模板题推荐:
小编在这里给大家推荐一道KMP的模板题,有心的OIers可以去做一下。
这里顺便把小编我的题解展示一下:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map>
#include<vector>
#include<string>
#include<cstring>
#include<queue>//头文件
using namespace std;
char a[1000001],b[1000001];//主串s->a[],模式串t->b[]
int p[1000001];
int qwq[1000001];//记录合法子串的开始位置
int tot;//记录查找到了几个合法子串
void pre()//p数组的预处理
{
p[1]=0;//p[1]初始化为0
for(int i=1,j=0;i<strlen(b+1);i++)//b[]的自我匹配过程
{
while(j>0&&b[i+1]!=b[j+1])
{
j=p[j];
}
if(b[i+1]==b[j+1])
{
j++;
}
p[i+1]=j;
}
}
void kmp()//KMP过程
{
for(int i=1,j=0;i<=strlen(a+1);i++)
{
while(j>0&&b[j+1]!=a[i])
{
j=p[j];
}
if(b[j+1]==a[i])
{
j++;
}
if(j==strlen(b+1))//符合则记录当前子串的开始位置
{
qwq[++tot]=i-strlen(b+1)+1;//不明白可以手推一下
}
}
}
int main()
{
scanf("%s%s",a+1,b+1);//输入
pre();
kmp();
for(int i=1;i<=tot;i++)//输出子串开始位置
{
printf("%d\n",qwq[i]);
}
for(int i=1;i<=strlen(b+1);i++)//最长前缀长度其实就是p[]
{
printf("%d ",p[i]);
}
return 0;
}


本文介绍了KMP算法的起源、暴力(BF)算法的原理,以及KMP算法的优势。KMP算法通过预处理得到一个数组p[j],用于在模式串匹配失败时避免不必要的比较,提高效率。文章提供了C++代码实现,并推荐了一道KMP算法的模板题。
4843

被折叠的 条评论
为什么被折叠?



