您的位置:首页 > 其它

字符串匹配KMP算法

2017-09-22 10:43 204 查看

1.解决的问题

假如有两个字符串,我们最有可能去寻找较短的一串是否在较长的一串中,也就是寻找子集的问题。例如:长串为A:“kmpalgorithm”,短串为B:“algo”。这里,我们称长串为“主串”,较短的为“模式串”。此时,我们用计算机的方式去思考如可解决上问题,显然暴力枚举是最简单的方式(我一直觉得当今算法带来的成本节省最迫切的是时间上的。):我们设置两层循环,外层用 i 来遍历主串字符,事实上用来确定子串是从哪里开始的(假设B是A的子串),所以这个变量需要记录,并且最坏情况下循环A.lenth()次,对于每一次循环 i ,我们对字串B用 j 来维护每一个字符,每次循环次数都是从(0~B.lenth()-1),每当发现 j > B.lenth(),则(0~B.lenth()-1)全部匹配完成,问题就解决了,否则 i 越界, j 越界都属于匹配不成功(B不是A的子串)。这样做的时间复杂度为O(n*m),如果数据量过大,计算机的计算时间增长过快,导致算法的不可用性。

2.算法优点

KMP算法来自Knuth,Morris,Pratt三人共同提出(由三人名字首字母得来),此算法其对于任何模式和目标序列,都可以在线性时间内完成匹配查找。我们来回顾之前我们是怎么解决问题的,并且找出可能优化的步骤:首先对于主串的字符我们是不可避免的都要进行一次遍历,这是一种消除不确定的做法,否则你漏掉某一个可能起始的字符,你一定不太放心它是否就是答案,所以第一步我们不能动,保留枚举做法,但是对于模式串问题,朴素模式匹配(上面的做法的一个名字)依然采用暴力枚举来解决,看似也不可替代,事实上在模式串不停地向后位移过程中,非要每次位移一个单位才能消除不确定性么?

如下例子:



首字符不匹配,向后位移模式串,直到首字符匹配:



此时首字符匹配,但问题是再向后匹配过程中到模式串的最后一位是D,而主串为空格:



此时,最关键的地方到了,我们要做的不是继续一格一格的位移模式串,而是实现跳跃,这次我们一次跳两格:



如上图,我们实现了跳两格操作,有同学问:你不是说为了防止遗漏不确定要枚举么?这里最大的优点在于就在于我敢拍着胸脯说:我这样做依然维护了避免不确定性这一原则,虽然发生了跳跃。我们回到位移之前,我们在字符D发生了不匹配现象,也就是说D之前的所有字符是成功匹配的,那么之前匹配好的串我们叫做F串,既然F串是成功的我们就要利用到这个信息,现在由于D的问题,我们不得不移动模式串,由于模式串的首字符或者说前缀字符是一定要与主串匹配,而我们已得到成功匹配的F串,也可以说在不得不位移的情况下,我们可以选择将前缀字符匹配到F中,并且是后缀优先匹配,因为我们要解决的是D的问题,这样就有了字符串前缀和后缀的概念。

我们用一个next[]的表来维护每个字符要移动时跳跃的步数,而这个步数就是最大模式串前缀和后缀重合的字符数。



接下来问题就快要解决了,我们知道了每个字符发生不匹配时需要跳几步,而且我都记录在了next[]数组里,用的时候查表即可。

接下来我们考虑如何生成next[],我们先把注意力集中到模式串,因为这个表是模式串的属性,于是问题转化为如何求解一个字符串的“部分匹配值”。

1.已知前一步计算时最大相同的前后缀长度为k(k>0),即P[0]···P[k-1];

2.此时比较第k项P[k]与P[q],如图1所示

3.如果P[K]等于P[q],那么很简单跳出while循环;

4.如果不等呢?那么我们应该利用已经得到的next[0]···next[k-1]来求P[0]···P[k-1]这个子串中最大相同前后缀,可能有同学要问了——为什么要求P[0]···P[k-1]的最大相同前后缀呢?是啊!为什么呢? 原因在于P[k]已经和P[q]失配了,而且P[q-k] ··· P[q-1]又与P[0] ···P[k-1]相同,看来P[0]···P[k-1]这么长的子串是用不了了,那么我要找个同样也是P[0]打头、P[k-1]结尾的子串即P[0]···Pj-1,看看它的下一项P[j]是否能和P[q]匹配。如图2所示



3.代码实现

#include<bits/stdc++.h>
using namespace std;

void make_next(string b, int next[], int len_b){
int i,k;
next[0]=0;
for(int i=1, k=0; i<len_b; i++){
if(b[i]==b[k])  k++;
if(k&&b[i]!=b[k])   k=next[k-1];
next[i]=k;
}
cout<<"next_array:";
for (i = 0; i < b.length(); ++i) cout<<next[i];
cout<<endl;
}
void KMP(string a, string b){
int n = a.length();
int m = b.length();
int next[20];
make_next(b,next,m);
for(int i=0, j=0; i<n; i++){
if(j&&a[i]!=b[j])   j=next[j-1];
if(a[i]==b[j])  j++;
if(j==m)    cout<<"Pattern occurs with shift:"<<i-m+1<<endl;
}
}

int main(){
string a, b;
cin>>a>>b;
KMP(a,b);
return 0;
}


4.算法的优化

待续。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: