KMP算法详解
来源:互联网 发布:js注释规范 编辑:程序博客网 时间:2024/06/06 13:02
KMP算法
KMP算法算得上是数据结构中比较难的算法之一了,大二那年我记得我好像看懂了,可是最近考研复习到这里的时候,我发现我看不懂了(所以之前还是一知半解啊)。在网上找了好多资料,综合了几个人的讲解终于看懂了。所以想着记录一下。说不定以后可以为他人提供一定得帮助。
首先说明,此篇是“面向原理”的算法说明,抛开代码,从原理的层面上来理解整个算法。
首先概括一下KMP算法的主要内容:
KMP算法是一种字符串匹配算法,KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next数组,数组的意思是指向模式串的下一个开始位置。时间复杂度O(m+n)。
KMP算法的主要计算过程,代码如下:
int KMP(char S[], char T[], int next[], int pos){ //利用模式串T的next函数求T在主串S中第pos个字符之后的位置的KMP算法。 int i = pos; int j = 1; while(i<=S[0] && j<=T[j]){ if(j==0 || S[i]==T[j]){ ++i; ++j; }//if else j = next[j]; }//while if(j > T[0]) return i- T[0]; else return 0; }
上面的代码很好理解,如果S[i] == T[j],表示当前位置的主串和模式串中的字符相同,i j各自加一;如果不同,j = next[j],表示S[i]!=T[j]的时候j指针下一步移动的位置。这里j的不是从T数组的初位置开始,而是一个恰当的位置。
KMP算法的难点在于next数组的建立,代码如下:
void get_next(char T[], int next[]){ int i = 1; next[1]=0; int j = 0; while(I <= T[0]){ //这里的T[0]存储T的个数 if(j==0 || T[i]==T[j]{ ++i; ++j; next[i] = j; }//if else j = next[j]; }//while}
整个KMP算法的核心就是这个next数组的建立,也就是确定当一个字符与主串不匹配的时候,我们要知道j这个指针要移动到哪里去?
我们首先来发现一下规律。
当i j运行到如上位置的时候,S[i] != T[j],这时候我们会想到的解决办法就是
很明显,因为i的位置的前面有s,我们就把j的位置移动到2上。那么,规律来了:
当匹配失败的时候,j要移动的下一个位置k,k应该满足这样的条件:
k前面的k-1个所有字符要和j前面的k-1个字符完全一致。
数学公式如下:
T[1 … k-1] = T[j-k+1 … j-1]
例子中
我们后来选取的k的位置的前面所有的字符和j前面k-1个完全一样(这里k=2),都是s。
我们来推导一下数学公式,来证明一下为什么直接将j移动到k的位置,而不是第一个位置。
当S[i] != T[j]时
S[i-j+1 … i-1] = T[1 … j-1]
T[1 … k-1] = T[j-k+1 … j-1]
=> S[i-j+1 … i-1] = T[1 … k-1]
所以直接保证了k的前k-1个字符串和S中i之前的k-1个字符完全一样。
所以我们的的目的就是找到匹配不成功的时候最合适的k的位置,这个k的位置要保证前k-1个字符和主串中i之前的k-1个字符相同且尽可能最长。
当出现这种情况的时候,我们的k应该是1还是4,很明显是4,我们希望的是这样:
而不是这样:
因为我们就是为了让k尽量的离初始位置远一些,也就是前k-1个位置的所有的字符串和主串中i之前的k-1个字符串的完全一致。
我们接着来归纳:
首先来看第一个,当j在第一个位置就和主串不匹配的时候:
S[i] != T[j],因为j = 1,j已经在最左边了,所以,j的下一个位置只能是0,即next[[1]] = 0;
而当j = 2,主串和模式串不匹配时:
此时j也就只有一个位置可以移动,那就是第一位,即next[[2]] = 1;
说这么多,可能有些同学还是没有理解,所以我们来用一个例子来说明一下。
eg:求“ababaaababaa”的next数组(from 王道论坛-2017数据结构 P269 第六题)
首先根据之前对j = 1,j = 2位置的讨论,next[1] = 0, next[2] = 1;这里应该没有疑问,那么接下来我们讨论第三个。
第三个位置T[3] = a,这时候前三个字符串为aba,所以第三个匹配不成功的时候,主串中S[i-2] = a, S[i-1] = b,在模式串中找不到这样的k,使得前k-1个位置的字符分别是ab或者是b,所以j= 3的下一个位置k只能为1,即next[3] = 1,大家不要忘记了,next[j]的值就是下一个要匹配的k的位置,这一点很重要,做题过程中要始终记得
当j = 4的时候,这时候和主串中已经匹配了的字符串一定是aba,所以当第四个位置不匹配的时候,我们下一个要找的k的位置就一定满足,前k-1个字符串为aba或者ba或者a(我们要找能满足条件的最长的子串,即k值尽可能的大),我们观察T数组中的前三个,只有当k为2的时候,前k-1个字串为a,即为满足条件的最长,我们来结合图片看一下
这里的主串只是为了更容易理解,所以怎么简单怎么来,前后可能不一致
即k位置的前k-1个和i前k-1个的字符完全一样,next[4] = 2;
接下来第五个位置,当T[5] != S[i]的时候,说明主串和模式串中当前位置的前四个都是已经匹配了的,即为abab,所以我们接下来找的位置k要满足前k-1个为abab、bab、ab、b中的满足条件的最长的。所以我们发现k = 3的时候,前两个为ab即为满足条件的最长的
所以此时next[5] = 3,即j = 5不匹配的时候,j的下一个位置为3
同理,第6个位置不匹配的时候,说明前面已经匹配了5个了,即ababa,所以接下来找的k满足前k-1个为ababa、baba、aba、ba、a中最长的,观察得:k = 4的时候,前k-1个为aba为满足条件的最长的串
此时的next[6] = 4,第6个位置不成功时的下一个查找位置为4
j = 7时,前面已经匹配了6个,即ababaa,所以满足条件的有ababaa、babaa、abaa、baa、aa、a中最长的,观察,k = 2时,前k-1个为a为最长的
此时next[7] = 2,第7个位置不成功时的下一个开始位置为2
j = 8时,前面已经匹配了7个,即ababaaa,所以满足条件的有ababaaa、babaaa、abaaa、baaa、aaa、aa、a中最长的,观察,k = 2时,前k-1个为a为最长的
此时next[8] = 2,第8个位置不成功时的下一个开始位置为2
j = 9时,前面已经匹配了8个,即ababaaab,所以满足条件的有ababaaab、babaaab、abaaab、baaab、aaab、aab、ab、b中最长的,观察,k = 3时,前k-1个为ab为最长的
此时next[9] = 3,第9个位置不成功时的下一个开始位置为3
j = 10时,前面已经匹配了9个,即ababaaaba,所以满足条件的有ababaaaba、babaaaba、abaaaba、baaaba、aaaba、aaba、aba、ba、a中最长的,观察,k = 4时,前k-1个为aba为最长的
此时next[10] = 4,第10个位置不成功时的下一个开始位置为4
j = 11时,前面已经匹配了10个,即ababaaabab,所以满足条件的有ababaaabab、babaaabab、abaaabab、baaabab、aaabab、aabab、abab、bab、ab、b中最长的,观察,k = 5时,前k-1个为abab为最长的
此时next[11] = 5,第11个位置不成功时的下一个开始位置为5
j = 12时,前面已经匹配了11个,即ababaaababa,所以满足条件的有ababaaababa、babaaababa、abaaababa、baaababa、aaababa、aababa、ababa、baba、aba、ba、a中最长的,观察,k = 6时,前k-1个为ababa为最长的
此时next[12] = 6,第12个位置不成功时的下一个开始位置为6
至此,整个过程就结束了,next数组就这样求出来了。是不是很简单!!!我这篇博客写的挺多,后面将一整个例子完完全全的一步一步的推导出来,所以篇幅比较长,不过,只要你耐下心来看,应该可以看懂的。
这是我第一次写博客,所以经验不足,表达能力欠佳,我已经用我认为最简单的想法来解释算法了,但是如果有不足的地方,请各位看官指正,互相学习。
- KMP算法详解 【KMP】
- KMP算法详解
- KMP算法详解
- KMP算法详解
- KMP算法详解
- KMP算法详解
- KMP算法详解 转帖
- KMP算法详解
- KMP算法详解
- KMP算法详解
- KMP算法详解
- KMP算法详解 转帖
- KMP匹配算法详解
- KMP算法详解
- KMP算法详解
- KMP算法详解
- 转:KMP算法详解
- KMP算法详解(转)
- Git 学习(篇二 --Git配置)
- 国内大数据交易平台
- 区分指向对象的常指针和常对象的指针
- Hibernate增删改查
- linux 用awk gsub将一行变成多行
- KMP算法详解
- 代理模式
- Material Design 系列 控制项目全局样式
- jquery动态增减选项卡
- Navicat for mysql 导入导出数据库
- React组件生命周期过程说明
- StackExchange.Redis通用封装类分享
- MySQL实战(七)数据库开发规范
- 解决 has incomplete type