本文主要讲述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. kubernetes入门(08)kubernetes单机版的安装和使用

    kubectl get - 类似于 docker ps ,查询资源列表 kubectl describe - 类似于 docker inspect ,获取资源的详细信息 kubectl logs - ...

  2. mysql(3)—— 内连接、外连接的区别

    先来看一下,内连接的语法: SELECT  XXX FROM XXX INNER JOIN XXX ON XXX; 这里 INNER 可以省略,在上一篇博客中我们对于笛卡尔积现象的研究中(http:/ ...

  3. Mac里安装Jmeter

    前提是需要安装jdk,参见http://www.cnblogs.com/fun0623/p/4703456.html 1.解压包 (双击apache-jmeter-2.13) 2.进去到解压后的bin ...

  4. python/零起点(一、列表)

    python/零起点(一.列表) 列表(list)list()可以强行转换数据类型为列表,列表是可迭代对象 列表是有序的,且列表是可变的数据类型 列表中的元素可以是(字符串.整型.元祖.列表.字典.集 ...

  5. Spring Cloud学习笔记-003

    服务提供者:向注册中心注册服务 1. 新建maven工程,骨架选择quickstart,工程名称:demo-member 2. 加入相关依赖: 3. 编写服务接口: 4. 在src\main\reso ...

  6. CLR-基元类型以及溢出检查

    =========(CLR via C#阅读笔记)======== 基元类型(primitive type): 基元类型也不做过多的解释,举个例子即可清晰的辨别 在java里曾使用过Sting s=& ...

  7. .Net Core 学习之路-基础

    .Net Core出来好久了,一直在了解,但始终没有应用到实际项目中.... 准备用.net core搞个SSO,才发现它和.net framework的变化并不是一点点... .net core还在 ...

  8. c++模板使用及实现模板声明定义的分离

    c++模板是编译器构造具体实例类型的模型,使类型参数化,是泛型编程的基础,泛型就是独立于特定类型. 一.模板分为函数模板和类模板两种. 函数模板:template <class 形参名,clas ...

  9. 【Swift】swift中懒加载的写法

    swift中懒加载的写法,直接上例子 (懒加载一个遮罩视图) lazy var dummyView: UIView = { let v = UIView() v.backgroundColor = U ...

  10. JS基本数据类型(typeof的返回结果)

    number(Infinity/NaN) string boolean function object(null.各种值装箱对象.内置对象.自定义对象) undefined 判断对象是否为某个[类/构 ...