您的位置:首页 > 其它

KMP算法之while循环部分

2016-05-15 01:28 239 查看
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。

关于KMP算法的基本原理,这里不再多做解释。当时看的时候,最困惑的部分是算法核心的一个while循环,所以这里也主要用一个具体的实例来详细讲解这个while循环。先上一段代码。

#include<iostream>
using namespace std;
int main()
{
char *str_src="bbc abcdab abcdabcd abcdabdef";
char *str_des="abcdabd";
int len_src=strlen(str_src);
int len_des=strlen(str_des);

for(int i=0;i<len_src;++i){
printf("%4c",str_src[i]);
}
cout<<endl;
for(int i=0;i<len_src;++i){
printf("%4d",i+1);
}
cout<<endl;
int next[100]={0};

//构造next,判断当前des串的字串的前缀和后缀是否相等
//以“ABCDABD”为例

/*
 - "A"的前缀和后缀都为空集,共有元素的长度为0;

  - "AB"的前缀为[A],后缀为,共有元素的长度为0;

  - "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;

  - "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;

  - "ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;

  - "ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;

  - "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。
*/

for(int i=1,k=0;i<len_des;++i){
while(k>0&&str_des[k]!=str_des[i]){
k=next[k-1];
}
if(str_des[k]==str_des[i]){
++k;
}
next[i]=k;
}
//结束构造next

for(int i=0,k=0;i<len_src;++i){
while(k>0&&str_src[i]!=str_des[k]){
k=next[k-1];
}
if(str_src[i]==str_des[k]){
++k;
}
if(k==len_des){
cout<<(i-k+2)<<endl;
}
}
return 0;
}
第41和53行的while循环是算法的核心部分,个人感觉也是比较难理解的地方。为了方便描述,接下来用s代替str_des数组。

  当我们在构造next数组的时候,需要时刻比较的是s[i]和s[k]是否相等,如果两者相等,简单的将k++,再令next[i]=k,表明以s[i]字符结尾时,当前前缀和后缀共有元素为k。
 
关键问题是遇到s[i]和s[k]不相等时,while循环是怎么处理的!
 考虑一下,当我们遇到s[i]!=s[k]时,真的没有得到任何有用的信息,只能让k从0再来了吗?其实,尽管s[i]!=s[k],但是我们可以知道,此时s[0]……s[k-1]和s[i-k]……s[i-1]这两段是成功匹配上了的,考虑一个具体的例子。

AAAABAAAAA
0123456789
  假设当i=9,k=4时,既然s[i]!=s[k],那么说明当以s[i]结尾时,前缀和后缀共有元素数量一定<k,同时s[0]->s[3]和s[5]->s[8]是匹配上了的。
 (1)此时我们可以知道,s[0]=s[5],s[1]=s[6]……s[3]=s[8],见下表红色标注数组下标部分。

  (2)由(1)的对应关系,为了更快速的得到next[i],我们就要充分利用s[0]->s[k-1]这段字串的前缀后缀关系。所以我们先看一下以s[k-1]结尾时,前后缀共有元素数量,具体计算过程不再多写,直接写出结果——next[3]=3。根据while循环内的执行语句,则k=3,这个k第(4)步再用
先考虑next[3]=3带给我们的信息:s[0]=s[1],s[1]=s[2],s[2]=s[3]。

  (3)结合(1)、(2),我们得到下表黄色标注部分

  (4)再一次判断s[i]和s[k]是否匹配,若不匹配,重复上述步骤(while循环)。

数组下标01234[b]56789
字符AAAABAAAAA
数组下标
   0123
数组下标
待匹配012待匹配
   现在应该已经发现了while循环的精髓所在—当s[i]!=s[k]时,充分利用next[k-1]的信息,来快速的得到next[i]。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: