KMP算法之while循环部分
2016-05-15 01:28
239 查看
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。
关于KMP算法的基本原理,这里不再多做解释。当时看的时候,最困惑的部分是算法核心的一个while循环,所以这里也主要用一个具体的实例来详细讲解这个while循环。先上一段代码。
当我们在构造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]这两段是成功匹配上了的,考虑一个具体的例子。
假设当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循环)。
现在应该已经发现了while循环的精髓所在—当s[i]!=s[k]时,充分利用next[k-1]的信息,来快速的得到next[i]。
关于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]这两段是成功匹配上了的,考虑一个具体的例子。
A | A | A | A | B | A | A | A | A | A |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
(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循环)。
数组下标 | 0 | 1 | 2 | 3 | 4 | [b]5 | 6 | 7 | 8 | 9 |
字符 | A | A | A | A | B | A | A | A | A | A |
数组下标 | 0 | 1 | 2 | 3 | ||||||
数组下标 | 待匹配 | 0 | 1 | 2 | 待匹配 |
相关文章推荐
- WPF 更换图片
- 归并排序
- IO流
- iOS开发——响应链(Responder Chain)的深入理解和代码示例
- Intellij jrebel 热部署
- android开发笔记之多媒体—VideoView播放视频
- 迷宫问题
- 农场灌溉问题
- [Android]Binder池的使用
- android 闪屏分析及解决方案
- Java 集合框架
- 计算机组成.其实机器也会出错.错误检验与纠错的数据编码
- 使用ssh-keygen设置ssh无密码登录
- web.config加密解密方法
- Java知识:(1)JRE和JDK
- 装载问题
- Web 安全登录
- 求图像的周长
- 使用nginx的proxy_cache做网站缓存
- Linux内核态的文件操作