KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。
KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。
具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。
要想知道KMP的高效性在哪里,我们就首先需要了解常规算法。
常规算法
text:需要被查询的文本
pattern:需要被匹配的字符
我们还是先看看伪码应该怎么写。
naive-string-matches(t,p)
n<- length(T)
m<-length(P)
for s<-0 to n-m
do if P[1--m]=T[s+1--s+m]
print s
这里的下标都是从1开始,这样也更符合我们的思考
#include<iostream>
#include<string>
using namespace std;
int match(const string& target,const string& pattern){
int t=target.size();
int p=pattern.size();
while(t>ti&&p>pi){
if(target[ti]===pattern[pi]){
ti++;
pi++;
}
else{
ti-=(pi-1);
pi=0;
}
}
if(pi==p)
return ti-pi;
else
return -1;
}
KMP算法
首先我们先定义一下什么是next数组;
next数组——覆盖函数
子串 | 值 |
---|---|
a | -1 |
ab | -1 |
aba | 0 |
abaa | 0 |
abaab | 1 |
abaabc | -1 |
abaabca | 0 |
如果不解释,你是否能看得出其中的规律?
就是
用通俗的话来讲,pattern的前k个字符和最后k个字符相同。
如何获得这一next数组
我们如果仔细分析,那么发现,这其实可以用递归式来表示。
注意,这里我们使用的是overlay名称来代替next这一称呼
#include<iostream>
#include<string>
using namespace std;
void computer_overlay(const string& pattern){
const int pattern_length = pattern.size();
int *overlay = new int[pattern_length];
int index;
overlay[0] = -1;
for (int i = 1; i < pattern_length; i++){
index = overlay[i - 1];
/*这是一个精彩的过程,将遍历index到*/
while (index>0 && pattern[index + 1] != pattern[i]){
index = overlay[index];
}
if (pattern[index + 1] == pattern[i]){
overlay[i] = index+ 1;
}
else{
overlay[i] = -1;
}
}
for (int i = 0; i < pattern_length; i++){
cout << overlay[i]<<endl;
}
}
int main(){
string a = "aabbabaa";
computer_overlay(a);
system("pause");
return 0;
}
这样我们就得到了next数组(overlay[])。
next[]的意义在于,我们在朴素算法中匹配失败时,需要将ti降到即ti-pi+1的位置上去,并且将pi降低到0。
现在,我们根本不需要这么做,将pi降低到index[pi]就可以了。
#include<iostream>
#include<string>
using namespace std;
//KMP算法 寻找一个字符串在另一个字符串中的位置
int kmp_find(string& target, string& pattern){
//获取pattern的next数组
int* overlay = new int[pattern.size()];
overlay[0] = -1;
int index;
for (int i = 1; i<pattern.size(); i++){
int index = overlay[i - 1];
while (index>=0 && pattern[i] != pattern[index+1]){
index = overlay[index];
}
if (pattern[i] == pattern[index + 1])
overlay[i] = index + 1;
else
overlay[i] = -1;
}
for (int i = 0; i < pattern.size(); i++){
cout << overlay[i] << endl;
}
//进行比对
int ti = 0, pi = 0;
while (pi < pattern.size() && ti < target.size()){
if (pattern[pi] == target[ti]){
ti++; pi++;
}
else if (pi == 0){
ti++;//这种情况下只让ti++
}
else{
pi = overlay[pi-1]+1;
}
}
if (pi == pattern.size())
return ti - pi;
return -1;
}
int main(){
string a = "sabcbcd";
string b = "bcbc";
cout << kmp_find(a, b) << endl;
system("pause");
return 0;
}
其中,这一段显得很突兀。
else if (pi == 0){
ti++;//这种情况下只让ti++
}
但是,这一段却很重要,因为所有overlay[i]==-1的i值最终都会指向0。
如果此时pattern[0]!=target[ti],那么唯一的选择就是ti进一位。
也就是说,KMP算法中,在无法匹配当前pi和ti时,除了将pi=overlay[pi-1]+1之外,在特殊的情况下,所需要的是ti++。
说到这里,我们还是对KMP算法做一个总结吧。
总体来说,这个算法很精妙,尤其是overlay数组(next数组)的建造过程也很漂亮的一个地方。
KMP的核心实在target[ti]和pattern[pi]失配时,pi的平移并不是暴力地回到0处,ti也不是到达上一次起点+1也就是ti-pi+1的位置。
这样就节省了大量的时间。
版权声明:本文为博主原创文章,转载请标明出处。