本文主要讲述KMP已经KMP的一种改进方法。若发现不正确的地方,欢迎交流指出,谢谢!

KMP算法的基本思想:

KMP的算法流程:

每当一趟匹配过程中出现字符比较不等时,不需回溯 i 指针,而是利用已经得到的部分匹配的结果将模式向右滑动尽可能远的一段距离后,继续进行比较。

S为目标串T为模式串,设 i 指针和 j 指针分别指示目标串和模式串中正待比较的字符。

开始时,令i=0,j=0。如果Si==Tj,则使i和j的值分别增加l;反之,i不变,j的值退回到j=next[j]的位置(即模式串右滑),然后再对Si和Tj进行比较。依次类推,直到出现下列两种情况之一

1.j值退回到某个j=next[j]时,有Si==Tj,则指针的值各增加1后,再继续匹配;

2.j值退回到 j=-1,此时令指针的值各增加1,也即下一次对Si+1和T0进行比较。

模式匹配KMP算法的时间复杂度为O(m+n),只有当模式与珠串之间存在许多“部分匹配”的情况下显得比朴素字符匹配算法快得多。但是KMP算法最大的特点就是指示主串的指针不需要回溯,整个过程中,对主串仅需从头至尾扫描一遍,这对处理从外设输入的庞大文件很有效,可以边读入边匹配,而无需回头重读。

跟朴素匹配算法的主要差异:

在于当Si != Tj的时候,朴素算法采用的是将Tj往前推一格,然后将j置为0,重新进行匹配,而KMP采用的方法是将j置为next[j],然后再匹配。

很显然,这里的next[j]是算法的核心

下面是next[j]的计算方法,以及代码的实现:

  1. void get_nextval( const char *s, int *nextval)
  2. {
  3. int len = strlen(s);
  4. int i = 0, j = -1;
  5. nextval[0] = -1;
  6. while( i < len-1 ){
  7. if( j == -1 || s[i] == s[j] ){
  8. ++i;
  9. ++j;
  10. if( s[i] != s[j] ){
  11. nextval[i] = j;
  12. }else{
  13. nextval[i] = nextval[j];
  14. }
  15. }else{
  16. j = nextval[j];
  17. }
  18. }
  19. return ;
  20. }

得到了next[j]之后,KMP算法的实现就很简单了,按照上面KMP的算法流程,可以很快写出代码:

  1. //s为匹配串
  2. //t为主串
  3. int kmp( const char *s, const char *t )
  4. {
  5. int k = -1;
  6. int nextval[N] = {0};
  7. int s_len = strlen(s);
  8. int t_len = strlen(t);
  9. get_nextval( s, nextval );     //get_nextval[]
  10. cout<<"nextval:"<<endl;
  11. for( k = 0; k < s_len; k++)
  12. cout<<nextval[k]<<" ";
  13. cout<<endl;
  14. int i = 0, j = 0;
  15. while( i < t_len && j < s_len ){
  16. if( j == -1 || t[i] == s[j] ){
  17. i++;
  18. j++;
  19. }else{
  20. j = nextval[j];
  21. }
  22. }
  23. if( j >= s_len ){
  24. return i-s_len;
  25. }else{
  26. return -1;
  27. }
  28. }

下面给出一个KMP的实现及测试代码:

  1. #include <iostream>
  2. using namespace std;
  3. #define N 100
  4. void get_nextval( const char *s, int *nextval);
  5. int kmp( const char *s, const char *t );
  6. //s为匹配串
  7. //t为主串
  8. int kmp( const char *s, const char *t )
  9. {
  10. int k = -1;
  11. int nextval[N] = {0};
  12. int s_len = strlen(s);
  13. int t_len = strlen(t);
  14. get_nextval( s, nextval );     //get_nextval[]
  15. cout<<"nextval:"<<endl;
  16. for( k = 0; k < s_len; k++)
  17. cout<<nextval[k]<<" ";
  18. cout<<endl;
  19. int i = 0, j = 0;
  20. while( i < t_len && j < s_len ){
  21. if( j == -1 || t[i] == s[j] ){
  22. i++;
  23. j++;
  24. }else{
  25. j = nextval[j];
  26. }
  27. }
  28. if( j >= s_len ){
  29. return i-s_len;
  30. }else{
  31. return -1;
  32. }
  33. }
  34. void get_nextval( const char *s, int *nextval)
  35. {
  36. int len = strlen(s);
  37. int i = 0, j = -1;
  38. nextval[0] = -1;
  39. while( i < len-1 ){
  40. if( j == -1 || s[i] == s[j] ){
  41. ++i;
  42. ++j;
  43. if( s[i] != s[j] ){
  44. nextval[i] = j;
  45. }else{
  46. nextval[i] = nextval[j];
  47. }
  48. }else{
  49. j = nextval[j];
  50. }
  51. }
  52. return ;
  53. }
  54. int main()
  55. {
  56. char s[N], t[N];
  57. while( cin>>s >>t ){
  58. int i = 0;
  59. i = kmp( s, t );
  60. cout <<"ans = " <<i <<endl;
  61. }
  62. return 0;
  63. }

测试如下:

KMP模式匹配问题的改进思想和方法

KMP的不足之处:

通过观察,我们可以在原有的KMP算法中,发现一个不足的地方,也就是我们将要改进的地方就是,因为,子串的出现是随机的,如果子串在主串出现的位置靠后的时候,KMP算法实在显得比较低效。现在我们给出一个例子来说明问题。

主串为:aspowqeursoolksnkhiozbgwoinpweuirabaac

子串为:abaac

容易看出,子串要到最后才会得到匹配,因此,我们提出我们的思想——从主串的首和尾同时进行匹配,那样,就可以提高算法的效率,并且,除了在这个方面提高算法效率以外,我们还想到,当 m >>n,,n>>0的时候(m为主串长度,n为子串长度),并且子串并没有在主串中出现的话,那么,在改进算法中,我们将不需要比较到最末才判断是否存在匹配的子串,而是通过剩下的字符数,来判断是否存在足够的字符与子串匹配,如果不足的话,那样就不存在,否则就继续匹配下去。

如何实现从主串末尾想串头开始匹配呢?

我们这里有两个方案:第一个方案是,把子串逆转,然后沿用旧的KMP算法中的next函数求出其逆转后的子串的next值,再用以进行匹配;第二个方案就是,不需要把子串逆转,而是采用一个新的next函数直接求出其逆转后的next值。

第一二个方案比较后,我们选择第二个方案。因为,在 n>>0的时候,明显地,在把子串逆转的时候同时需要多一个字符串来存放,并且,在不同的匹配都需要一个新的字符串,这样就大大地浪费空间了,除此之外,第一个方案至少要做遍历子串两次,而第二个方案只需要遍历子串一次就可以了。所以我们决定采用构建一个新的next函数来求出其逆转后的子串next值。

我们新的next函数的思想就是,把末字符看成是首字符,然后,仿照KMP算法中的next函数的实现方式最终实现的。现在,我们给出实现的新的next函数:

  1. void nextres( char* p, int *next, int n )
  2. {
  3. int i, j, k;
  4. i = n-1, j = -1;
  5. *next = -1;
  6. k = n;
  7. while( i > 0 ){
  8. if( j == -1 || *(p+i) == *(p+k-j-1)){
  9. i--, j++;
  10. if( *(p+i) != *(p+k-j-1) )
  11. *(next+n-i-1) = j;
  12. else
  13. *(next+n-i-1) = *(next+j);
  14. }
  15. else
  16. j = *(next+j);
  17. }
  18. }

在得到逆转后的子串的next函数后,我们就可以进行串的匹配了。其基本思路同原KMP算法,下面我们就给出匹配过程的实现:

  1. int march( char* mainhead, char* head, int mainlen, int lenth, int *next1, int *next2 )
  2. {
  3. int i, j, k, l, m;
  4. i = 0, j = 0, k = mainlen-1, m = lenth-1, l = 0;
  5. while( (m>0 && j<lenth) || lenth == 1 ){
  6. if( lenth == 1 && ( *(mainhead+i) == *(head+j) ||
  7. *(mainhead+k) == *(head+m)))
  8. return 1;
  9. if( j == -1 || *(mainhead+i) == *(head+j))
  10. i++, j++;
  11. else
  12. j = *(next1+j);
  13. if( l == -1 || *(mainhead+k) == *(head+m)){
  14. k--;
  15. l++;
  16. m = lenth==2?m-l:m-1;
  17. }else{
  18. l = *(next2+1);
  19. if( l != -1 )
  20. m = m-l;
  21. else
  22. m = lenth-1;
  23. }
  24. if( k-i < m-j )
  25. return 0;
  26. }
  27. if( m <= 0 || j >= lenth)
  28. return 1;
  29. else
  30. return 0;
  31. }

新的KMP算法在某种程度上的确可以提高模式匹配的效率。除此以外,新的模式匹配算法还能提早结束不必要的匹配。

from: http://blog.csdn.NET/cyh_24/article/details/8162436

 
0

KMP及其改进算法的更多相关文章

  1. 【Java】 大话数据结构(8) 串的模式匹配算法(朴素、KMP、改进算法)

    本文根据<大话数据结构>一书,实现了Java版的串的朴素模式匹配算法.KMP模式匹配算法.KMP模式匹配算法的改进算法. 1.朴素的模式匹配算法 为主串和子串分别定义指针i,j. (1)当 ...

  2. 大话数据结构(8) 串的模式匹配算法(朴素、KMP、改进算法)

    --喜欢记得关注我哟[shoshana]-- 目录 1.朴素的模式匹配算法2.KMP模式匹配算法 2.1 KMP模式匹配算法的主体思路 2.2 next[]的定义与求解 2.3 KMP完整代码 2.4 ...

  3. 排序系列 之 简单选择排序及其改进算法 —— Java实现

    简单选择排序算法: 基本思想: 在待排序数据中,选出最小的一个数与第一个位置的数交换:然后在剩下的数中选出最小的数与第二个数交换:依次类推,直至循环到只剩下两个数进行比较为止. 实例: 0.初始状态 ...

  4. 第四十一课 KMP子串查找算法

    问题: 右移的位数和目标串没有多大的关系,和子串有关系. 已匹配的字符数现在已经有了,部分匹配值还没有. 前六位匹配成功就去查找PMT中的第六位. 现在的任务就是求得部分匹配表. 问题:怎么得到部分匹 ...

  5. 数据结构开发(14):KMP 子串查找算法

    0.目录 1.KMP 子串查找算法 2.KMP 算法的应用 3.小结 1.KMP 子串查找算法 问题: 如何在目标字符串S中,查找是否存在子串P? 朴素解法: 朴素解法的一个优化线索: 示例: 伟大的 ...

  6. 读论文《BP改进算法在哮喘症状-证型分类预测中的应用》

    总结: 一.研究内容 本文研究了CAL-BP(基于隐层的竞争学习与学习率的自适应的改进BP算法)在症状证型分类预测中的应用. 二.算法思想 1.隐层计算完各节点的误差后,对有最大误差的节点的权值进行正 ...

  7. POJ 3155 Hard Life(最大密度子图+改进算法)

    Hard Life Time Limit: 8000MS   Memory Limit: 65536K Total Submissions: 9012   Accepted: 2614 Case Ti ...

  8. 字符串类——KMP子串查找算法

    1, 如何在目标字符串 s 中,查找是否存在子串 p(本文代码已集成到字符串类——字符串类的创建(上)中,这里讲述KMP实现原理) ? 1,朴素算法: 2,朴素解法的问题: 1,问题:有时候右移一位是 ...

  9. 字符串模式匹配算法系列(三):Trie树及AC改进算法

    Trie树的python实现(leetcode 208) #!/usr/bin/env python #-*- coding: utf-8 -*- import sys import pdb relo ...

随机推荐

  1. 《深入实践Spring Boot》阅读笔记之一:基础应用开发

    上上篇「1718总结与计划」中提到,18年要对部分项目拆分,进行服务化,并对代码进行重构.公司技术委员会也推荐使用spring boot,之前在各个技术网站中也了解过,它可以大大简化spring配置和 ...

  2. Linux入门:usermod - 修改用户帐户信息

    一.什么是usermod? usermod 命令通过修改系统帐户文件来修改用户账户信息usermod [options] user_name选项(options)-a|--append ##把用户追加 ...

  3. Oracle复合B*tree索引branch block内是否包含非先导列键值?

    好久不碰数据库底层细节的东西,前几天,一个小家伙跑来找我,非要说复合b*tree index branch block中只包含先导列键值信息,并不包含非先导列键值信息,而且还dump了branch b ...

  4. spring9——AOP之AspectJ对AOP的实现

    从上述的实验中可以看出BeanNameAutoProxyCreator对于AOP的实现已经和完美了,但是还有两点不足之处: 1,对于切面的实现比较麻烦,既不同类型的通知切面要实现不同的接口,而且一个切 ...

  5. java的分数类

    概述 分数类在算法中非常重要, 而在java中不那么重要,java基础类库提供 了biginteger了,提供类似方式, package 组合数学; public class Fraction { p ...

  6. fetch简明学习

    前面的话 Fetch API 提供了一个 JavaScript接口,用于访问和操纵HTTP管道的部分,例如请求和响应.它还提供了一个全局 fetch()方法,该方法提供了一种简单,合乎逻辑的方式来跨网 ...

  7. Linux-centos-7.2-64bit 安装配置mysql

    2018-04-12 安装在/usr/local/下,配置文件在/etc/my.ini 1.下载mysql安装包到 /usr/local/software cd /usr/local/software ...

  8. Java:import com.sun.awt.AWTUtilities;报错

    参考网址:http://stackoverflow.com/questions/860187/access-restriction-on-class-due-to-restriction-on-req ...

  9. Python open()函数文件打开、读、写操作详解

    一.Python open()函数文件打开操作 打开文件会用到open函数,标准的python打开文件语法如下:open(name[,mode[,buffering]])open函数的文件名是必须的, ...

  10. 【机器学习】Iris Data Set(鸢尾属植物数据集)

    注:数据是机器学习模型的原材料,当下机器学习的热潮离不开大数据的支撑.在机器学习领域,有大量的公开数据集可以使用,从几百个样本到几十万个样本的数据集都有.有些数据集被用来教学,有些被当做机器学习模型性 ...