KMP算法的一些误区及其优化
来源:互联网 发布:星际航行概论 知乎 编辑:程序博客网 时间:2024/05/26 17:49
16年12月26日再次编辑
昨晚在看清华严蔚敏的数据结构,重温了一遍KMP算法。严奶奶讲的特别详细特别透彻。发现自己还是有一些东西想岔了,这里重新修补下原来的博文。
问题主要集中在next[]数组的优化方面,在原来的地方用红色重写一下。
*********************************我是原来博文的分隔符********************************
这两天在看哈工大王宏志老师的算法设计入门课,讲到了KMP算法,这个之前也学习过,但一直没有搞的特别明白。于是花了两天的时间,看博客什么的,比如http://www.aboutyun.com/thread-9994-1-1.html就讲的很好。总之现在终于稍微清楚一点了,有一些相关的误区和优化想记录下来.。代码均在xcode(GCC)上可通过,都是很浅的东西,不知道有没有比我还小白的人看。
1. HOW TO GET next[]
(1) 明确next数组是针对子串PATTERN的,与原字符串S无关!//有一个相互匹配的问题,两个字符串,一般长的是S。或者,不重复的是S。(为什么?因为复杂度与p的长度有关,也与重复性有关。)
(2)next数组记录的是前缀-后缀的最大长度,而不是个数. 前缀中每一个都含有首字符,后缀中每一个都含有末字符。比较时是类似队列那种依次向后的比较方法。可以分为4种情况:
(3) 最笨的方法,非递归,没有出现k=next[k] or k=next[k-1] 这种情况
void GetNext(char* p,int next[]){ next[0] = 0; int k = 0; int j = 1; while (j < strlen(p) ) { //k是前缀下标,j是后缀下标 if(k!=0 && p[k]!=p[j]) //分四种情况,根据k是否等于0(或>0,一样的,因为K不会<0)和P[K]是否等于p[j]。 { k--; //唯一一个需要回溯k的,如“aadaaa",最后一位a与d不匹配,则缩小前缀比较前一个是否与最后的a匹配。 if (p[j]==p[k]) { if(k!=0) {next[j]=k; //记录此时的前缀,如"aadaaa",记录1,暂时是错误的,会在下一次循环中再加1. } else if(k==0) {next[j]=1; //或=k+1,这里是可以合并简化的点 } } } else if(k==0) { if(p[j]==p[k]) {next[j]=1; //或=k+1,这里是可以合并简化的点 k++;} else next[j]=0; //或=k,这里是可以合并简化的点 j++; } else {next[j]=k+1; //即k!=0 && p[j]=p[k]时,或为next[j]=next[j-1]+1 j++; k++;} } for (int j=0; j<strlen(p); j++) printf("%d ",next[j]);}也可以不依赖k,所有next[j]都用next[j-1]或数字来表示(不用前缀,用后缀)
while (j < strlen(p) ) { //p[k]表示前缀,p[j]表示后缀 if(k!=0 && p[k]!=p[j]) { k--; if (p[j]==p[k]) { if(k!=0) {next[j]=next[j-1]; j++; } else if(k==0) {next[j]=1; } } } else if(k==0) { if(p[j]==p[k]) {next[j]=1; k++;} else next[j]=0; j++; } else {next[j]=next[j-1]+1; j++; k++;} }(4)
好了,非递归的方法很笨,接下来递归。
前后不变,中间改为:
for (j=1;j < strlen(p);j++ ) //for循环更清晰 { while (k>0 && p[k]!=p[j]) //也可以用if,其实是一样的,while会减少循环次数 { k=next[k-1]; } if (p[k]==p[j]) { k++; } next[j]=k; }
2. 优化kmp数组。
关于为什么要优化和另一种k=-1,next[0]=-1的写法,可以参考上文提到的http://www.aboutyun.com/thread-9994-1-1.html。
这里转载一下代码 (严蔚敏的数据结构课算法与之类似,求得的next数组首位是0,1位是1,此后为该位之前的最大相同前后缀长度+1)貌似用||的写法才能在next数组中加上优化。
void GetNextval(char* p, int next[]) { int pLen = strlen(p); next[0] = -1; int k = -1; int j = 0; while (j < pLen - 1) { //p[k]表示前缀,p[j]表示后缀 if (k == -1 || p[j] == p[k]) { ++j; ++k; //较之前next数组求法,改动在下面4行 if (p[j] != p[k]) next[j] = k; //之前只有这一行 else //因为不能出现p[j] = p[ next[j ]],所以当出现时需要继续递归,k = next[k] = next[next[k]] next[j] = next[k]; } else { k = next[k]; } } }
这里的优化是通过对next[]数组的改动完成的,在KMP中仍然是 j=next[j]。但这里的next数组相当于最大前后缀右移一位,左边补
上-1,如果想直接利用最大前后缀来计算,不妨在KMP函数中优化。代码为:
void KmpSearch(char* s, char* p){ int i = 0; int j = 0; while (i<strlen(s)&&j<strlen(p)) { if (s[i] == p[j]) { j++; i++; } else if(j>0) { if(next[j]==0) j=next[j-1]; while(next[j]>0) { j=next[next[j-1]-1]; //不断递归 } } else if(j==0) { i++; } } if (j == strlen(p)) {printf("%d\n" , (i - j));j=0;} else printf("-1");}
回过头来说说k=-1时next数组的优化,这里的-1其实对每个重复的字符头记录了“头”的位置,比如”ABCABCABC“,
对应”-1 0 0 -1 0 0 -1 0 0“,而不是 ”000123456“这点很妙,等于当末位(最坏时)失配时,直接回到头了,而不用再比一次,发现
ABC==ABC,还是不匹配,再从头开始比。
例子char p[]="abcabc";
char s[]="abcababcabc"; 可以自己动手试试。
严蔚敏的数据结构课是这么解释的,对每一个字符(第i位),找它next[I]位的字符,如果二者重复则用next[next[i]]来替换next[i],相当于再次递归next[],我原来觉得是只往回找了一次,但其实回找的那个字符也对应的是它之前的 字符(如重复),所以效率提高是彻底的。
课上举的例子,“aaaab”,优化前对应01234(即-1 0 1 2 3,因为严奶奶的课字符串首位是字符长,第一位,即真正的第一个字符,对应第1位而不是第0位),优化后对应 00004(-1 -1 -1 -1 3)
而上述代码,则是在重复子串失配时,不断递归到上一次的位置,如”ABCABCABC“,假如在末位“C”不匹配时,用第一个C代替(肯定还是不匹配)。没有直接用k=-1时的代码效率高,但也只差1次。为什么呢?比如“ABABABAB",重复偶数次,当最后一个B不匹配时,j可以递归到0,而当”ABABAB",即,重复奇数次时,j只能到第一个“AB”的“B”,再与s比较。最多一次结束。可以考虑一下,这种最坏情况是什么样子?
3.什么是最坏,以及时间复杂度
明天再写
算了随便写写得了
(1)每次动j-next[j]位,j越大,next[j]越小,动的越少,比较的次数就越多,算法越慢。next数组体现了P的重复度,对没有优化的KMP,重复高,nexT值大,最坏的情况就是 一个子串 p=aaaaaaa,然后s=aaaaaaab.....这样子,next[j]==j-1,比到最后一位发现不对,只能挪
1位,那就退化到暴力搜索了。。。。时间复杂度仍不是线性的。不过这种情况相对罕见。
(2)对优化后的,有兴趣的自己算一算吧。
总之,优化还是很重要的! 不对的地方欢迎指正。
但愿这下没有不对的地方了。。。。
- KMP算法的一些误区及其优化
- KMP算法及其优化
- KMP算法及其优化
- KMP算法及其优化算法
- 经典算法之KMP算法及其优化
- 字符串查找(KMP算法及其优化)
- KMP算法的优化
- kmp算法的优化
- KMP算法的一些细节
- KMP算法的介绍及其Java实现
- KMP及其改进算法
- kmp算法及其拓展
- KMP算法及其实现
- KMP及其改进算法
- KMP算法及其应用
- SEO优化过程的一些常犯误区
- SEO优化过程的一些常犯误区
- KMP 算法并非字符串查找的优化
- kmalloc - 内存申请
- 第九章:接口
- 生产者消费者的实现与思考
- sea.js
- Problem E: IP地址
- KMP算法的一些误区及其优化
- 小伙的java之旅(5)——数组,常用类。
- muduo库的事件框架
- 阿里云ECS+VPC+弹性公有云
- MATLAB: 用MATLAB发送邮件(以163邮箱为例)
- JSTL
- I/O流具体例子
- 开发测试篇-android测试-介绍(翻译自android training官网)
- Grouping by Query