本文主要讲述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. Python内置函数(21)——tuple

    英文文档: The constructor builds a tuple whose items are the same and in the same order as iterable's it ...

  2. spring-oauth-server实践:授权方式四:client_credentials 模式的refresh_token?

    spring-oauth-server入门(1-13)授权方式四:client_credentials 模式的refresh_token? 有效期内的反复申请access_token获取失效日期不变! ...

  3. Vue全家桶

    简介 “简单却不失优雅,小巧而不乏大匠”. Vue.js 是一个JavaScriptMVVM库,是一套构建用户界面的渐进式框架.它是以数据驱动和组件化的思想构建的,采用自底向上增量开发的设计. 为什么 ...

  4. yum 安装Apache

    1.查看是否安装Apache,命令:  rpm    -qa    httpd 2.yum install httpd ,yum安装Apache 3.chkconfig    httpd  on  s ...

  5. mongodb聚合的使用

    聚合: 主要用于计算和统计等,类似sql种的sum() avg() db.集合.aggregate( { 管道:{表达式} } ) 常用的管道: $group:将集合中的文档按照字段进行分组 $mat ...

  6. Ubuntu下安装最新sublime

    1. Install the GPG key: wget -qO - https://download.sublimetext.com/sublimehq-pub.gpg | sudo apt-key ...

  7. 如何用webgl(three.js)搭建一个3D库房-第二课

    闲话少叙,我们接着第一课继续讲(http://www.cnblogs.com/yeyunfei/p/7899613.html),很久没有做技术分享了.很多人问第二课有没有,我也是抽空写一下第二课. 第 ...

  8. Python面向对象——基本继承

    1.基本继承图解 1.1实例化一个Contact类的对象c 1.2实例化一个Supplier类的对象s 1.3访问对象的属性 1.4访问对象s的方法 1.5类变量详解 如果从新定义c.all_cont ...

  9. Java知IO

    ---恢复内容开始--- Java将IO(文件.网络.终端)封装成非常多的类,看似繁杂,其实每个类的具有独特的功能. 按照存取的对象是二进制还是文本,java使用字节流和字符流实现IO. 流是java ...

  10. HTML笔记04---计时事件

    JavaScript运动01 计时事件 1.语法:var t=setTimeout("javascript语句",毫秒); setTimeout() 方法会返回某个值.在上面的语句 ...